Carta MagnaMadrid

Máster de Introducción a Machine Learning

Este documento recoge los apuntes realizados sobre el curso de Juan Gabriel Gomila Máster en Machine Learning - Aprende R y Python desde cero, y pretende ser una versión resumida del mismo, destacando los aspectos más importantes de la teoría.

Machine Learning en R. Introducción

R es un entorno de programación creado para el análisis estadístico y gráfico de datos, al cual se han añadido muchos paquetes con diversas funcionalidades. La gran ventaja es que es gratuito, a lo cual se suma su sintaxis sencilla e intuitiva, y su enorme comunidad de usuarios, que se manifiesta en el creciente volumen de paquetes añadidos a la CRAN (Comprehensive R Archive Network). Además, R no se utiliza de forma aislada, sino que normalmente se conjuga con RStudio (menos si estudias en la UPM, que trabajas con la consola), un entorno integrado que está pensado para la programación con R, y que en nuevas actualizaciones incluirá Python, aparte de que, como se verá en este documento, con el paquete reticulate se pueden incluir otros lenguajes para trabajar. De hecho, en este documento hay secciones en las que se programa con Python.

Un recurso útil para crecer en el conocimiento de R es consultar R-Bloggers, una herramienta creada por usuarios de R que contiene entradas de blogs de cientos de usuarios sobre diversos temas (es un foro básicamente). Otras alternativas para el aprendizaje y para compartir código con la comunidad son RPubs y el hashtag Tidytuesday, que consiste en una serie de retos semanales que propone la comunidad de R en Twitter para el análisis de distintos marcos de datos. Sobre este último recurso existe además un podcast que a mi parecer es bastante interesante.

Instalación de R

Para la instalación en Windows tanto de R como de Rstudio basta con ir a los enlaces que se han adjuntado y seguir las instrucciones. Se podría hacer también por consola, tal y como se puede ver en las instrucciones de instalación. Una vez instalado, se puede recurrir a la función de ayuda para ir familiarizándose con las funciones elementales. Para realizar consultas basta con ejecutar las siguientes instrucciones.

Otras opciones para buscar ayuda son los foros R-Project, de Facebook, y StackOverflow.

Los Paquetes

Una vez se ha instalado R, su gran potencial se alcanza con la instalación de los paquetes. Un paquete es un conjunto de funciones y/o de datos que se pueden instalar en R para añadir funcionalidades. Para instalar el paquete se emplea la instrucción install.packages(), y una vez instalado, para utilizar aquello que contiene, se carga en el entorno con la función library(). Un ejemplo es tidyverse, que es un paquete que sirve para facilitar el análisis de datos en R.

Otro ejemplo es el paquete magic, que presenta la función magic(x), donde x es la dimensión de la matriz. Esta función permite crear cuadrados mágicos, que son aquellas matrices cuyos elementos suman lo mismo en fila, columna y diagonal.

     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    7    6   35   34   15   14
[2,]    8    5   33   36   16   13
[3,]   27   26   19   18   11   10
[4,]   25   28   20   17    9   12
[5,]   23   22    3    2   31   30
[6,]   21   24    1    4   29   32

Machine Learning en Python. Introducción

Instalación de Anaconda

Para trabajar con Python, una de las herramientas de mayor utilidad para trabajar es Anaconda Navigator. Existen dos versiones, la 2.X y la 3.X, que se dividieron a partir de la actualización de Python 2.7. Así, las versiones 2.7 y 3.7 son dos ramificaciones diferentes de mejoras en Python, siendo la 2.7 diseñada para sistemas operativos antiguos. Pueden convivir en el mismo sistema operativo varias versiones de Python, luego se pueden descargar las dos ramificaciones sin problemas para trabajar.

Para instalarlo en Windows basta con seguir las instrucciones que se dan en el archivo .exe tras su descarga desde el enlace adjuntado. Ya instalado, al abrirlo, se puede ver que Anaconda es un IDE (Integrated Development Environment) bastante sencillo, pues realmente es una plataforma para el lanzamiento de otros entornos de desarrollo.

Gestor de Paquetes

Hay muchas formas de instalar paquetes en Python, pero la más sencilla y eficaz de ellas es pip, que es un paquete que sirve a modo de sistema para gestionar la instalación de diferentes softwares creados en Python. Entonces, se ha de instalar primero pip, para lo cual se puede acceder al siguiente enlace. Este gestor tiene actualizaciones de forma periódica, luego será conveniente actualizarlo para que no haya problemas al instalar paquetes (la consola notificará que se está usando una versión antigua de pip al instalar paquetes). Para instalar actualizaciones de pip hay que hacer “pip install –-update pip” en la consola. Realizado esto, para instalar un paquete con pip hay que hacer “pip install ‘nombre del paquete’”. Para comprobar que la librería se ha cargado de forma correcta se puede hacer en consola, por ejemplo, para el paquete pandas, python –c “import pandas”.

Como se acaba de ver, se ha creado un bloque de código con instrucciones en Bash. Esto es posible si se tiene cargado el paquete reticulate en el entorno, el cual permite introducir bloques de código de otros lenguajes distintos a R en Markdown. En el caso previo se ha especificado eval = FALSE, luego no se ejecuta ninguna instrucción del bloque. En caso de que sí se quisiera ejecutar estos comandos de Bash, bastaría con cargar este paquete.

Así pues, ahora se pueden crear bloques de código en otros lenguajes de programación tales como Python, Bash o SQL entre otros. Para ello, se sustituye en el bloque r por el nombre del lenguaje con el que trabajar. En la opción insert que presenta cualquier archivo Markdown se pueden ver las opciones de lenguaje a elegir para los bloques.

Hello

Paquetes para el Aprendizaje Automático con Python

Los paquetes más utilizados en Python para el análisis de datos son los siguientes:

  1. Pandas: es el paquete más utilizado al ser el más versátil. Permite leer y manipular marcos de datos, hacer análisis estadísticos básicos, y hacer operaciones básicas como crear subconjuntos, concatenar datasets o manejar los datos faltantes (NAs) entre otros.
  2. Numpy: es el equivalente de Matlab para Python. Sirve para el trabajo con matrices y contiene funciones matemáticas como las transformadas de Fourier. Contiene funciones para generar números aleatorios con arrays n-dimensionales, lo cual permite entre otras cuestiones generar marcos de datos “basura” o dummy para demostrar que un análisis funciona.
  3. Matplotlib: es el paquete estándar para la obtención de gráficos 2D. Tiene muchas alternativas para la manipulación de todo tipo de gráficos tales como histogramas, diagramas de barras apilados y no apilados, diagramas de dispersión, diagramas de cajas, diagramas de violines, diagramas de errores, mapas de calor o diagramas de densidad espectral.
  4. Ipython: ofrece un entorno para cálculos interactivos a través de una interfaz de desarrollo. Permite generar notebooks.
  5. Scikit-learn: en ella están los métodos y algoritmos clásicos de modelado. Sirve para realizar análisis predictivos.

Los Entornos de Desarrollo Integrado

Un entorno de desarrollo es un software que proporciona un editor de lenguajes de programación. Ahora bien, ¿qué IDE es el más adecuado para trabajar con Python? De entre los entornos dados, IDLE es el más básico y el que viene por defecto en Python, pero el formato que da para hacer informes es pobre, por lo que no se recomienda su uso. Jupyter y Spyder son los más conocidos para Python, y vienen integrados en Anaconda. Jupyter tiene dos componentes básicos que son una web app en la cual cada fragmento de código se puede tener en un bloque o celda, y otra parte que permite crear un documento para su descarga en múltiples formatos entre los que se incluyen HTML, Markdown, LaTeX, Notebook de Python o incluso como script de Python. También imágenes se pueden exportar en muchos formatos (png, jpg, svg…). Es una tecnología tan utilizada que la mayoría de blogs de Python son archivos HTML creados a partir de estos Notebooks de Jupyter o de IPython. Por su parte, Spyder es un potente entorno de desarrollo similar a Rstudio que se encuentra enfocado al desarrollo y que trae muchos paquetes instalados para tal fin.

Las Cinco Etapas del Data Science

El crecimiento de los datos sigue una función exponencial tal que hay estimaciones que aproximan un volumen de datos para 2025 de 180 Zetabytes. En este marco, la ciencia de datos tiene por objetivo obtener una visión, estructurada o no, de un conjunto de datos. Este proceso de visionado pasa por cinco etapas, en las cuales hay que hacerse la pregunta correcta en todo momento. Lo primero es saber enmarcar el problema, saber qué se tiene y qué se desea obtener, esto es, realizar las preguntas adecuadas. La segunda es conocer de qué recursos se dispone para adquirir y preparar los datos, limpiarlos y filtrarlos. Una vez los marcos de datos están a punto, el tercer paso es hacer la exploración, etapa en la que cobran relevancia los gráficos. Tras el análisis exploratorio se puede utilizar un modelo ya dado o crear un modelo y evaluar su actuación, conocer su fiabilidad, es decir, por último, se pone a punto el modelo creado y se sacan conclusiones en base a los resultados, momento en el cual la detección de errores tiene mucho valor. El último paso es la elaboración del informe para la comunicación de los resultados y la puesta en práctica de las acciones en base al análisis realizado. Dicho esto, en cada etapa del análisis se puede volver al paso anterior, no se trata de un proceso unidireccional.

Introducción a los Modelos Predictivos

Los modelos predictivos son más un arte que una ciencia, y el elegir un modelo viene a ser fuertemente condicionado por el marco de datos que sea objeto de estudio. Además, cada día se crean 2.5 exabytes siendo un exabyte 1018 bytes de información y esta velocidad en el crecimiento del volumen de los datos es lo que genera la demanda de analistas de datos.

Existen dos clases de análisis:

  1. Retrospectivo: se trata de entender la historia. Sirve para ver errores pasados tales como los aumentos del precio de un producto o servicio, ver ofertas, etc.
  2. Predictivo: se trata de un enfoque que da una visión al dato, busca predecir el futuro. Sirve para la detección futura de muertes, estafas, engaños, etc.

Una diferencia crítica del dato frente a otros recursos como petróleo es que es inagotable e instantáneo. EL dato en sí no vale, hay que refinarlos, pulirlos, modelizarlos, y se requiere de herramientas como Python y R para hacerlo. Otro detalle fundamental análisis es para un momento, no sirve para un futuro.

Los Algoritmos

La primera pregunta para abordar este aspecto es ¿qué es el modelo predictivo? Es un conjunto de algoritmos estadísticos que al aplicarse a datos históricos devuelven una función matemática útil para un contexto concreto o empresa. En este contexto, la estadística nos da las relaciones, el volumen y la distribución de los datos. Es el eje fundamental del modelo de decisión del aprendizaje automático. Los algoritmos son las rectas que nos darán las ecuaciones sobre las relaciones entre los variables. Hay múltiples algoritmos y se puede clasificar la mayoría de ellos en:

  • Supervisados: son aquellos en los que los datos históricos tienen un valor de salida además de los datos de partida. Esto quiere decir que modelo usa ambos datos. Ejemplos de estos algoritmos son la regresión lineal, la regresión logística y los árboles de decisión.
  • No supervisados: son aquellos que no necesitan de variables de salida en los datos históricos para crear su propio modelo. Un ejemplo de estos algoritmos puede ser un clustering, una segmentación de usuarios.

Las herramientas más potentes que se emplean para la creación de estos modelos, y que sean Open Source, son R y Python, al tener un conjunto de paquetes diseñados con estos fines de limpieza de datos y su modelado. Otra herramienta importante son los datos históricos, y es que, para construir un modelo se precisa de estos datos pasados. El modelo también sirve para averiguar la falta de datos.

Para ver si el modelo es bueno, se dividen los datos históricos en dos conjuntos, que son el marco de entrenamiento (datos históricos) y el marco de validación (datos futuros). Esta división no es arbitraria, tiene una serie de criterios en función de lo que se desee, siendo la más habitual la validación cruzada, y siendo la proporción más frecuente aquella que recoge un 80% de los sujetos para el marco de entrenamiento, y el 20% restante para el marco de validación.

El objetivo de la mayoría de estos modelos es elaborar una función matemática que tendrá una serie de parámetros que se deduzcan de los datos históricos. En la regresión lineal el modelo crea una función lineal a partir del marco de datos, mientras que en la regresión logística no da un polinomio, sino otra ecuación matemática del tipo:

\[P= \frac{1}{1+e^{-(\alpha+\beta x)}}\]

Por último, todo lo que se hace es siempre en el contexto de una empresa, buscando entonces que solucione un problema. Los objetivos han de ser claros, bien definidos. Por ejemplo, optimizar los anuncios (CTR) para maximizar las ventas, predecir la tasa de crimen según la alineación de un equipo de fútbol, predecir la ratio de fallo de una pieza y su coste, o modelizar la ratio de pérdida de compradores en un e-commerce (la mayoría de compradores de un videojuego se dan en los primeros días, más del 80% de ventas en la vida del juego), o la pérdida de jugadores online en los primeros días.

La Ciencia de los Datos & La Regla de Pareto

La ciencia de datos es una matriz de conocimientos para modelos predictivos, una ciencia interdisciplinar que une las ramas de la ciencia computacional, las matemáticas y la estadística, y el análisis empresarial. En suma, se ha de saber de estadística, algoritmia, herramientas y técnicas, y del contexto legal-empresarial. Además, la creación de modelos predictivos sigue un principio básico de la estadística, la regla de Pareto, que enuncia que las cosas importantes de la vida se dividen en 80/20. Esto quiere expresar que, en el marco de los modelos predictivos, la limpieza de datos es la parte más importante, la que consume el 80% del tiempo, y el 20% restante se destinará al proceso de crear el modelo y establecer las predicciones.

Aplicaciones & Ejemplos de la Ciencia de Datos

Un ejemplo de aplicación de los modelos predictivos son las recomendaciones en Linkedln que salen en “Otros perfiles vistos”. Forma parte de las reglas de asociación, es decir, definir qué conjuntos o grupos se compran o miran de forma conjunta (esto también lo hace Amazon). El targeting de anuncios online es otro ejemplo. En este caso el publisher es la web o la aplicación, mientras el anunciante o advertiser es quien sube el anuncio. El CTR (click through rate) es el número de veces que se da click en un anuncio por cada impresión, y es una medida de la garantía de un advertiser (permite saber si el publisher es o no es el adecuado). El CTR es muy pequeño, inferior al 1%, por lo que las ratios son muy bajas y es difícil trabajar con ello. Se han de limpiar mucho los datos para crear modelos. Otro ejemplo es la predicción del crimen en base al historial de crímenes creados por árboles de clasificación. En este caso, siempre se tienen árboles de decisión calientes, es decir, se recalcula el modelo de forma diaria incluyendo en el marco de datos los crímenes del día anterior. Los terroristas también emplean estas técnicas para realizar atentados en aquellos lugares donde pueden hacer el máximo daño con el mínimo esfuerzo. Otro ejemplo son las pulseras Fitbit o los acelerómetros de IPhone o Android, que son capaces de medir la aceleración en 3D y con ello conocer el estado del sujeto. Para ello, clusterizan los datos de aceleración en 3D (técnicas APC o single value position). También Moneyball es otro ejemplo, en este caso en el mundo del análisis deportivo. La historia que se narra, basada en hechos reales, habla de la temporada 2002/03 de béisbol de los Oakland Athletics, en la que el gerente del equipo, Billy Beane, contrató a un economista, Paul DePodesta, para dirigir el proyecto deportivo de esa temporada, es decir, para llevar a cabo la labor de ojeador y realizar los fichajes. Ante esta tarea, DePodesta creo un modelo para fichar jugadores baratos que tuvieran un potencial latente para ser grandes estrellas. Para ello utilizó datos históricos y se dio cuenta de que hasta entonces sólo se valoraba un jugador por lo que hacía durante el partido, pero obviando muchas otras métricas. Incluyó el porcentaje que se pasaba de fiesta, salía en prensa, se quedaba en la base y otros predictores más para llevar a cabo la planificación de la plantilla de aquella temporada histórica para los Oakland. Actualmente estos modelos son imprescindibles para la mayoría de equipos en todos los deportes (menos para el Arsenal, cuyos directivos se han cargado al 7.5% de toda la plantilla incluyendo al equipo de analistas con la excusa de la pandemia). Y estos modelos son también muy utilizados en las start-ups.

En conclusión, ¿qué es el Aprendizaje Automático? Es todo aquello que hacen los ordenadores que se asemeja al comportamiento humano. Así, el Machine Learning, o Aprendizaje automático, hace referencia a la capacidad de una máquina o software para aprender mediante la adaptación de ciertos algoritmos de su programación respecto a cierta entrada de datos en su sistema.

Introducción al Aprendizaje en R

Calculadora

El lenguaje R se puede utilizar como calculadora para llevar a cabo operaciones básicas, como las que se muestran a continuación. También tiene símbolos de interés como pi, infinito, valores perdidos o desconocidos (NA) o el símbolo que indica que no es un valor numérico (Not a Number, NaN). Además, en caso de que los valores introducidos sean de gran tamaño o muy pequeños, R introduce la notación científica (e).

            Suma         Producto         División  División Entera 
        2.000000         8.000000         1.333333         1.000000 
           Resto            Resta         Potencia             Raíz 
        2.000000        -3.000000        32.000000         0.031250 
     Número Pi            Tau       Infinito Menos Infinito  Valor Perdido 
      3.141593       6.283185            Inf           -Inf             NA 
  Not a Number 
           NaN 
[1] Inf
[1] NaN
[1] 1.1259e+15
[1] 1.525879e-05

Funciones & Combinatoria

En R se hallan también funciones específicas para realizar operaciones más complejas, como la raíz cuadrada, exponenciales de e, logaritmos neperianos, logaritmos en base diez u obtener el valor absoluto entre otros. Cabe destacar la función log(x) es el logaritmo neperiano de x. Para expresar la base que se desee se hace logx() o log(número, x), donde x es la base. Se puede poner de forma explícita el parámetro base en log, definiendo así la función como log(z, base = x).

       Raíz Cuadrada       Neperiano de e  Logaritmo en base 3 
                   4                    1                    2 
Logaritmo en base 10 
                   2 

Además, se tienen funciones para realizar operaciones de combinatoria, como calcular el factorial de un número (n!) u obtener el coeficiente binomial de n sobre m, que se define matemáticamente como se muestra en la siguiente fórmula y que permite generar x grupos de m sujetos sobre un conjunto de n elementos. Como ejemplo se tiene que el factorial de 7 es 5040 y el coeficiente binomial de 7 sobre 3 es 35.

\[\binom{n}{m} = \frac{n!}{m!(n-m)!}\]

[1] 5040
[1] 35

A este respecto, cabe recordar que no se puede hacer el factorial de un número negativo y que, por convención, el factorial de cero es uno.

Trigonometría

Las funciones trigonométricas en R trabajan con radianes, luego si se quiere expresar en grados se habrá de realizar una conversión, esto es, hace falta multiplicar por pi y dividir por 180 para obtener en radianes el ángulo que se tiene en grados.

\[rad = \frac{x\pi}{180}\]

   Seno de 90º Coseno de 180º Tangente de 0º    Seno de 60º  Coseno de 60º 
     1.0000000     -1.0000000      0.0000000      0.8660254      0.5000000 
[1] 1

Hay un pequeño margen de error, luego la tangente de 90º no dará infinito ni la tangente de 180º dará cero de forma exacta.

[1] 1.633124e+16
[1] -1.224647e-16

También se encuentran funciones para el cálculo de los arcosenos, arcocosenos y arcotangentes, que dan el ángulo en radianes cuyo seno, coseno y tangente, respectivamente, coinciden con el valor especificado. Por ejemplo, tomando el valor devuelto por el ángulo de 60º, se tendrá:

[1] 1.047198
[1] 60

Los tipos de Decimales

En R, al igual que en la mayoría de sistemas informáticos, para guardar los números se utiliza la técnica del punto flotante, lo cual quiere decir que cualquier número es guardado como mantisa, esto es, como un número exacto, y luego un exponente que sirve para cambiar la posición de la coma. Esto se ofrece con la función print(). Además de ésta, se tienen otras funciones como round(), para redondear, floor(x), para dar la parte entera por defecto, ceiling(), para dar la parte entera por exceso, y trunc(), cuya utilidad es truncar la parte entera.

[1] 1.414213562
   Print    Round    Floor  Ceiling    Trunc 
1.414214 1.414000 1.000000 2.000000 1.000000 

Hay que tener en consideración que al operar se acumulan errores mínimos. Por esto si se resta a la raíz de dos al cuadrado el valor de dos exacto no se obtendrá cero como resultado, sino que dará un residuo elevado a menos quince o menos dieciséis, tal y como se tiene en la siguiente operación. Así pues, si se quiere mucha precisión en un proyecto, hay que tener cuidado con estos errores acumulados.

[1] 4.440892e-16

En R no puede trabajar con más de 16 cifras decimales, de modo que si se piden más dígitos podrá dar error directamente el comando, como al hacer print(pi,40), o realizará cálculos erróneos a partir de la posición decimosexta, como se muestra en el siguiente ejemplo:

[1] 3.1415926535897931

Otra cuestión de interés es que round redondea a la cifra par en caso de indecisión. Así, 1.25 será 1.2 y 1.35 será 1.4 como se muestra a continuación. Al hacer round(x, 0) se obtiene un redondeo entero, que es el redondeo por defecto. Se puede explicitar el parámetro digits y poner el número de decimales al que se quiere redondear, lo cual permite cambiar el orden de los parámetros suministrados a la función.

[1] 1.4
[1] 1.6
[1] 1
[1] 2.3568

Estas funciones al trabajar con valores negativos se orientan a redondea o truncar hacia la baja, esto es, van en orden inverso. De este modo, hay que pensar en términos absolutos para entender cómo funcionan estas funciones.

           Floor   Floor Negativo          Ceiling Ceiling Negativo 
               3               -4                4               -3 
           Trunc   Trunc Negativo 
               3               -3 

Variables & Funciones

El lenguaje R funciona con objetos, es un lenguaje orientado a objetos. Una variable y una función son ejemplos de objetos. Al crear las variables, que simplemente son contenedores de los elementos que se definen, éstas aparecerán en el entorno (ventana superior derecha en RStudio). Para definir las variables se utiliza el operador de asignación, que en R puede tomar tres formas, que son “<-”, “->” y “=”; y hay que tener en cuenta que R es Case Sensitive, es decir, que distingue entre minúsculas y mayúsculas, de modo que se pueden tener variables distintas con las mismas letras diferenciándolas mediante el uso de mayúsculas. Además, en R, al igual que en Python, es el contenido el que define el tipo de la variable, esto es, no es preciso declarar la variable como sí se hace en otros lenguajes como Java o C#. En cuanto a las funciones, se definen por medio de function(), entre cuyos paréntesis se engloban los parámetros a suministrar a la función y que va seguida de unas llaves que engloban las operaciones que se llevarán a cabo con dicha función.

[1] 8
[1] 16
[1] 52.7568

Se pueden declarar funciones más complejas con más de dos parámetros como las que se muestran a continuación. Por otra parte, las funciones pueden devolver otros objetos más allá de un valor numérico. Es más, típicamente devolverán listas, marcos de datos o vectores, para lo cual es preciso utilizar return al final de la función.

[1] 273.0411
[1] 1035.721
[1]  9 25 40

Se utiliza ls() para listar elementos de los que se dispone en el entorno y se tiene rm() para cargarse uno o más elementos del entorno. Para eliminar todos los elementos del entorno se puede hacer rm(list=ls()). Otra opción es emplear la escoba del entorno (en la ventana superior derecha).

 [1] "cuadrado"      "doble2"        "funpropia"     "Negflotante"  
 [5] "operaciones"   "Pflotante"     "plusParameter" "sumaseq"      
 [9] "symbolsR"      "trigg"         "var4"          "var5"         
[13] "var6"         
 [1] "cuadrado"      "doble2"        "funpropia"     "Negflotante"  
 [5] "operaciones"   "Pflotante"     "plusParameter" "sumaseq"      
 [9] "symbolsR"      "trigg"         "var4"          "var6"         
character(0)

Los Números Complejos

Un número complejo, a + bi, se puede crear en R con la función complex() de dos modos. O bien complex(real=a, imaginary=b), que es la forma binómica, o bien complex(modulus=sqrt(a2+b2), argument= atan(Im(x)/Re(x)=arccos(Re(x)/Mod(x)=arcsin(Im(x)/Mod(x)), siendo ésta la forma polar. También se puede crear directamente escribiendo a+bi sin necesidad de utilizar complex(). Con Re(x) se extrae la parte real de un número complejo y con Im(x) su parte imaginaria. Con Mod(x) se obtiene el módulo de un número complejo y con Arg(x) su argumento. Finalmente, con Conj(x) se da su conjugado.

[1] 3+3i
[1] 2+3i
[1] 3
[1] 3
[1] "complex"

Se pueden realizar operaciones con números complejos, como las siguientes.

[1] 15+15i
[1] -6+18i
[1] 0.4153846-0.3230769i

Con la función sqrt(as.complex(-x)) se obtiene la raíz de un número negativo. Sin embargo, si se hace sqrt(-x) se obtendrá como salida error.

[1] 0+2.236068i

Al hacer la raíz cuadrada de un número complejo, R devuelve de los dos resultados posibles el positivo, es decir, da siempre el resultado con la parte real positiva en R, pero hay una solución que tiene la parte real negativa. Para obtenerla simplemente se multiplica por menos uno. Todo depende de cuál de las dos soluciones que devuelve la raíz se quiera emplear. Se pueden realizar más operaciones con estos números complejos como obtener el seno, el coseno o hacer que sea la potencia de e.

[1] 1.817354+0.550251i
[1] -1.817354-0.550251i
[1] -8.35853+18.26373i
[1] 0.530921-3.590565i
[1] -3.724546-0.511823i

Para obtener el módulo se ejecuta el comando Mod(x), como ya se ha mencionado, y éste no es sino la raíz de la suma de los cuadrados de las partes real e imaginaria.

\[Mod(z) = \sqrt{Re(z)^2+Im(z)^2}\]

[1] 4.242641

En R, el argumento de un número complejo, que se obtiene mediante Arg(x), se devuelve en radianes y va de pi a menos pi, (-pi, pi], y este intervalo refleja que pi está incluido, pero no así menos pi. Este parámetro se puede calcular como la división de la arcotangente de la parte imaginaria entre la parte real. En este caso, si la parte real es 0 habrá error.

\[Arg(z) = \theta = \arctan{\frac{Im(z)}{Re(z)}}\]

También se puede definir como el arcocoseno de la parte real entre el módulo.

\[ \theta = \frac{\arccos(Re(z))}{Mod(z)}\]

Y como el arcoseno de la parte imaginaria entre el módulo.

\[ \theta = \frac{\arcsin(Im(z))}{Mod(z)}\]

[1] 0.7853982

Para obtener el conjugado de un número complejo se hace Conj(x). Es el mismo número pero cambiando el signo de la parte imaginaria.

[1] 3-3i

Además, solamente conociendo el módulo y el argumento de un número complejo se puede recuperar éste en su forma binomial mediante la expresión siguiente.

\[z = Mod(z)(\cos(\theta_z)+\sin(\theta_z)i)\]

En tal situación, en R bastaría con usar el módulo y el argumento conocidos como parámetros de la función complex.

[1] 1.414214+1.414214i
[1] 2
[1] 0.7853982

Introducción al Aprendizaje en Python

Las Constantes

En Python las funciones matemáticas, a excepción de las que involucran a los números complejos, se recogen en un módulo llamado math, que en Anaconda se encuentra cargado por defecto (para versiones antiguas), pero que si se emplea algún editor diferente como Sublime Text 3 es preciso llamar con la función import. En el caso de los números complejos se tiene el módulo cmath, que permite separar estas operaciones con números complejos del resto y hacer así más suave la curva de aprendizaje. Para utilizar las funciones del módulo math tras su importación se recurre a la sintaxis del punto, por la cual se pone la palabra math seguida de un punto y, a continuación, la función del módulo que se quiera ejecutar, por ejemplo, la raíz cuadrada.

3.0

Para llamar a las constantes sucede exactamente lo mismo; se recurre a la sintaxis del punto y se escribe el nombre de la constante (pi, e, tau).

3.141592653589793
2.718281828459045
6.283185307179586

Otro valor interesante que recoge el módulo math es el infinito, que se puede llamar con la sintaxis del punto flotante o mediante la función float, la cual también se puede aplicar a las constantes utilizadas previamente.

inf
-inf
inf

En Python existen dos formatos para guardar los números decimales, float y double. La diferencia entre float y double es que double tiene el doble de precisión que un float (normalmente 16 decimales frente a ocho decimales de float).

También se encuentra el NaN (Not a Number) en Python al igual que estaba en R. Hay dos formas de acceder a él, como se muestra a continuación.

nan
nan

Los errores matemáticos se ofrecen como ValueError: math domain error. Expresan que tal operación no existe, como puede ser la raíz de un número negativo o el logaritmo de cero entre otras. Por otra parte, si Python no tiene precisión para hacer una operación matemática porque excede su capacidad, devolverá OverflowError: math range error. Esto nos dice que no puede realizar la operación porque excede el número de decimales que es capaz de procesar. A diferencia de R, estos errores son más explícitos y ayudan al programador a entender mejor qué está fallando (por ello no devuelve NaN cuando la precisión en decimales se ve superada, sino que especifica un tipo de error). Para mostrar los errores en Python en un archivo Markdown se ha de poner en la cabecera del bloque error=True.

Error in py_call_impl(callable, dots$args, dots$keywords): ValueError: math domain error

Detailed traceback: 
  File "<string>", line 1, in <module>
Error in py_call_impl(callable, dots$args, dots$keywords): ValueError: math domain error

Detailed traceback: 
  File "<string>", line 1, in <module>
Error in py_call_impl(callable, dots$args, dots$keywords): OverflowError: math range error

Detailed traceback: 
  File "<string>", line 1, in <module>

El valor NaN es realmente como un comodín. Es un objeto matemático del que no se conoce el valor pero que se sabe que es matemático. Por ello si se hace math.pow(math.nan, 0) da uno, porque al elevar cualquier número a cero se obtiene uno.

1.0

Por su parte math.hypot(x, y) devuelve la raíz cuadrada de la suma de los cuadrados de x e y. Por el hecho de que NaN sea un objeto matemático, si se utiliza con NaN esta operación se obtendrá un resultado, no dará error, es decir, se respeta la operación, siendo la salida obtenida, salvo excepción, un valor NaN.

3.605551275463989
nan
inf

Operaciones Aritméticas & Redondeo

Las operaciones aritméticas son igual que en R, salvo ligeras diferencias (por ejemplo, para hacer la división entera se hace //). Se muestran las operaciones básicas a continuación.

[3, -2, 15, 1.5, 32, 1]

En cuanto a las operaciones de todas ellas dependen del módulo math. Por ejemplo se tienen la función math.ceil(x, r) que sirve para el redondeo a la alza, mientras que math.floor(x, r) permite un redondeo a la baja. También se encuentra math.trunc(x), que trunca el valor de x, quita la parte decimal sin tenerla en consideración.

8
7
7

Otra función interesante es math.copysign(x, y), que lo que hace es copiar el signo de y en x. Si y es negativo hará que x también lo sea. Por ejemplo, esto sirve para dar signo al cero (por la izquierda o por la derecha, algo común al trabajar con límites). Se recomienda no poner enteros con funciones de math, porque este módulo trabaja con floats y en alguna ocasión puede generar conflictos.

-3.0
-0.0
0.0
-5.0

La función math.fabs(x) devuelve el valor absoluto de x.

8.0

La instrucción math.factorial(x) devuelve el factorial de un número. Para hacer combinatoria se puede recurrir a la librería scipy, pero también se puede hacer solamente con math mediante la operación math.factorial(x)/(math.factorial(y)\(\times\)math.factorial(x-y)).

120
10.0

La función math.fmod(x, y) calcula el resto de la división de x entre y. Otra forma es mediante math.remainder(x, y), si bien esta función es de las versiones más actualizadas (desde Python 3.7 en adelante); y también es posible emplear %, y en este caso se puede trabajar además con números enteros. Con reticulate se ha configurado la última versión que se tiene incorporada en Anaconda de Python actualmente, que es la 3.6, de modo que la función math.remainder(x, y) dará error. Si se dispone de versiones más recientes instaladas en Windows, como la versión 3.8, sí se podrá utilizar math.remainder(x, y).

1.0
[1.0, 1]

Mediante la función math.modf(x) separa la parte entera de la parte decimal de x.

(0.6699999999999999, 5.0)

Otra función de gran utilidad es math.gcd(x, y), que dará el máximo común divisor entre dos números.

28

Además, para saber si un número es finito se emplea math.isfinite(x) y para saber si es infinito es math.isinf(x).

[True, False, False, True]

Para saber si dos números son iguales a nivel computacional, por el error que arrastra el cálculo, se emplea la función math.isclose(x, y), que permite saber si son cercanos. Una opción como argumento de esta función es hacer la tolerancia relativa en porcentaje. Por ejemplo, se haría, para un error del orden de 10-9, math.isclose(x, y, rel_tol=1e-09).

False
True
True
False

Operaciones de Entrada & de Salida

Como operación de salida se tiene print, que permite mostrar por consola lo que en ella presenta. Tiene varios parámetros como sep, el separador, y end, que marca el final de aquello que se imprime en consola. En caso de trabajar con números y strings a la par existen varias alternativas. Se puede hacer str(x) si x es numérico y se quiere enlazar con una frase. También se puede poner una coma separando el string de la parte numérica, y recientemente, se ha añadido la opción format, que trabaja con la sintaxis del punto. Por ejemplo:

El Real Madrid tiene en sus vitrinas 13 Copas de Europa.
El Real Madrid tiene en sus vitrinas 13 Copas de Europa.
El Real Madrid tiene en sus vitrinas 13 Copas de Europa.
El Real Madrid tiene en sus vitrinas 13 Copas de Europa.
[2, 3, 4, 5, 6, 7].

Si se desea que el usuario introduzca información se puede ejecutar el comando input, que por defecto dará como string todo aquello que escriba el usuario. Para que se transforme en un número se haría int(input()). Python es un lenguaje no tipado (el contenido define el contenedor), lo que hace que sea más dinámico pero que haya que estar más atento a saber de qué tipo es la variable con la que se trabaja.

Operadores de Decisión

En Python el bloque de código se organiza mediante el tabulador, mediante la sangría, no con llaves, como se daba en R. Son las estructuras condicionales, a saber, if, elif, else y switch case.

Se canta un cinco.

Mediante elif se pueden generar opciones múltiples.

Enhorabuena, amigo, continúa así.

Finalmente, switch-case es una instrucción que se ejecuta cuando se tiene un número finito de salidas u opciones. Se encuentra de forma explícita en R y en Python se puede definir una función que actúe como switch-case mediante la declaración de funciones con el comando def.

Funciones Matemáticas Avanzadas

Existen funciones matemáticas en Python más avanzadas que las que se vieron en secciones anteriores. Una de ellas es math.exp(x), que sirve para elevar e a x. Esta función es más precisa que hacer math.ex. Otra opción alternativa, que tiene la misma precisión que math.ex, sería el comando math.pow(math.e, x).

7.38905609893065
7.3890560989306495
7.3890560989306495

Para hacer la operación e elevado a x menos uno se hará math.expm1(x). Esto es más preciso que hacer math.exp(x) y luego restarle uno; y esto se hace más patente cuando se trabaja con muchos decimales, como se puede comprobar a continuación.

1.718281828459045
1.718281828459045
1.0000050000166667e-05
1.0000050000069649e-05

Si se quieren calcular logaritmos se hará math.log(x, base). Si no se añaden argumentos se ejecuta el logaritmo neperiano. Como las bases dos y diez son muy utilizadas se tienen las funciones math.log2(x) y math.log10(x).

4.02535169073515
5.807354922057605
5.807354922057604
3.0

Si se pretende hacer el logaritmo de un número más uno con la máxima precisión posible se hará math.log1p(x, base), tal y como sucedía con las exponenciales de e.

9.99995000033333e-06
-10.512925464970229

Para hacer la raíz cuadrada de un número se tiene la instrucción math.sqrt(x).

8.0

En cuanto a las funciones trigonométricas como math.sin(x), también van en radianes, como en R. Para pasar de radianes a grados en este caso sí se tiene una función específica, math.degrees(x), y para pasar de grados a radianes se haría math.radians(x). Por ejemplo, el seno, coseno y tangente de 60 grados se obtendría del siguiente modo.

0.8660254037844386
0.5000000000000001
1.7320508075688767

Además, existen las funciones arcoseno, arcocoseno y arcotangente.

90.0
0.0
45.0

Con math.hypot, que ya se ha visto previamente, se devuelve la norma euclídea de un vector, nos da el módulo de un vector. Para saber qué ángulo forma este mismo vector con el eje horizontal, algo que se utiliza mucho en videojuegos para colisiones, se recurre a math.atan2(y, x), y hay que tener en cuenta que las coordenadas se introducen al revés en esta función. Para un vector (3,4) se tendrían los siguientes módulo y ángulo con el eje de abscisas.

5.0
0.9272952180016122

Las funciones hiperbólicas, que son menos usadas, son aquellas que incluyen la h al final como math.sinh(x).

0.0
1.0
0.0

Para la función de distribución acumulada de la normal se tiene math.erf(x), siendo su función complementaria math.erfc(x), que no es más que uno menos math.erf(x). Estas dos funciones se utilizan con frecuencia en proyectos de Inteligencia Artificial para suavizar los datos.

0.9999911238536323
8.876146367641617e-06

Otra función poco utilizada es la math.gamma(x), que devuelve la función gamma de x. Esta función gama devuelve el factorial de x - 1, pudiendo ser x tanto entero como decimal, razón por la cual se dice que la función gamma es el factorial generalizado (cabe mencionar aquí entonces que la función gamma de cero no existe). También se tiene el logaritmo neperiano en base gamma con la función math.lgamma(x).

1.0
120.0
33.50507345013689
Error in py_call_impl(callable, dots$args, dots$keywords): ValueError: math domain error

Detailed traceback: 
  File "<string>", line 1, in <module>

Definir Funciones

En Python existen tres grandes familias de funciones, que son aquellas dadas por el propio sistema, como las que se han visto hasta ahora; las funciones definidas por el programador, y las conocidas como funciones anónimas o funciones lambda, que son declaradas de formas diferentes al estándar. Por otro lado, se define como método aquella función que forma parte de una clase, siendo una clase un modelo donde se redactan las características y acciones comunes de un grupo de objetos. La diferencia radica en que el método, aparte de contenerse en una clase, contiene un argumento o parámetro más conocido como self, que hace referencia a cada objeto que se crea a partir de la clase. Siendo más puristas respecto a la oración anterior, se definiría parámetro como aquello que representa un valor que la función espera al llamarla, mientras que el argumento representaría el valor que se suministra a un parámetro cuando se realiza la llamada a la función. A continuación, se enseña la diferencia entre la llamada a una función creada por uno mismo y la llamada a un método, que exige previamente instanciar la clase, es decir, crear un objeto o instancia a partir de la clase definida que contiene el método al que se quiere llamar.

9
10

Cabe destacar que tanto en un método como en una función, si no hace falta que se devuelva ningún objeto, se puede prescindir del return final. En tal caso, lo que se devuelva será NonType, de modo que, si se quiere duplicar lo que se imprime y con tal objetivo se multiplica por dos dará error, porque la función o el método no devuelven ningún objeto.

Buenas, transeúnte.
Error in py_call_impl(callable, dots$args, dots$keywords): TypeError: unsupported operand type(s) for *: 'NoneType' and 'int'

Detailed traceback: 
  File "<string>", line 1, in <module>

Además, con return se puede devolver más de una variable mediante una tupla, lo cual se haría como return (x, y,…, n). Este paréntesis es opcional, pero se recomienda para exponer el código de forma más clara. Además, se pueden asignar valores por defecto a argumentos como def(x = a, y = 4), tal que si se llama a la función sin poner los argumentos ésta te devuelva la operación con dichos argumentos por defecto.

(17, 3, 100)
(34, -20, 980)
(34, 6, 980)

Argumentos Variables & Funciones Lambda

Las funciones con varios argumentos nacen de la necesidad de que hay ocasiones en las que no se sabe el número de argumentos con los que puede trabajar una función. Para construirlas, simplemente se pone un asterisco delante del nombre que se le dé al argumento. El asterisco sólo se usa para declarar el argumento, es decir, dentro de la función únicamente se pondrá el nombre del argumento.

36

Un ejemplo más complejo sería el siguiente.

30

Para finalizar esta sección se tienen las funciones lambda o anónimas, cuyo uso se ha visto incrementado los últimos años. Para declararlas no se usa def, sino lambda. Su sintaxis es del tipo “lambda x: operación(x)”. Como se observa en el siguiente bloque de código, no se ha hecho sangría, se ha escrito la operación a continuación, es decir, su sintaxis es mucho más reducida. Así, en este contexto, estas funciones son de gran utilidad para ejecutar funciones de filtro, mapeado y reducción de información por etapas del paquete functools.

16

El uso de una función lambda acoplado a una función de filtro sería el siguiente.

[5, 6, 7, 8]

Si en lugar de una función de filtro se usa una función de mapeo se obtendría lo siguiente. Lo que hace esta función es transformar el marco en base a la función que expreses, que en este caso es multiplicar cada elemento por dos.

[2, 4, 6, 8, 10, 12, 14, 16]

Como se ha dicho, también se pueden usar las funciones lambda para la reducción de la información por etapas.

36

En la última función lo que se hace es almacenar en y lo que se suma, y se suma a cada paso cada elemento de la lista contenida en x, que es la lista creada en data. Esto quiere decir que lo que se ha hecho es un sumatorio de una forma más compleja. Ejemplos de Scikit.

Como recursos adicionales se puede acceder al siguiente repositorio de GitHub, que contiene numerosas cheat-sheets, o a este repositorio, el cual ofrece una cheat-sheet muy resumida de funciones para realizar análisis de datos en Python; y también se puede acceder a DataCamp, que entre muchos otros recursos tiene una sección dedicada a cheat-sheets.

Estructuras de Datos en R

Tipos de Datos & Vectores

El vector es una estructura básica de datos de R que consiste en una secuencia ordenada de datos. El lenguaje R dispone de muchos tipos de datos entre los que cabe destacar los tipos atómicos logical (lógicos), integer (números enteros, Z), numeric (números reales, R), complex (números complejos, C) y character (palabras). En R todos los elementos que contiene un vector han de ser del mismo tipo y en caso de que se quiera utilizar objetos de distinto tipo se ha de recurrir a otras formas de estructuras como las listas generalizadas o los marcos de datos.

Las opciones para definir un vector son c(), de concatenar, scan(), que escanea datos escritos en consola, rep(a, n), que lo que hace es coger el elemento a y repetirlo n veces, y fix(), que permite editar un vector de un modo visual. Si se crea un vector que contenga diferentes tipos de datos R los transformará en el tipo de dato que tenga más jerarquía, siendo esta jerarquía character > complex > numeric > integer > logical. Con scan() se abre en la consola una entrada para que se introduzcan los valores y cuando estén todos escritos se pulsa dos veces Enter y se crea el vector. Esta función tiene varios parámetros entre los que cabe destacar what, que permite señalar de antemano el modo atómico de los datos. Con fix(vector) se abre una pequeña ventana para la edición del vector. Con scan() además se puede hacer scan(url) y poner los datos de un dataset que esté en la red, y también sirve para archivos locales, en cuyo caso se pondrá la ruta.

[1] 1 2 3 4 5
[1] "PythoR" "PythoR" "PythoR" "PythoR" "PythoR"

Para poder comprobar la clase del vector y de hecho demostrar la existencia de la jerarquía de modos atómicos de datos, se tiene la función class().

[1] "character"
[1] "complex"
[1] "numeric"
[1] "integer"

Progresiones Aritméticas & Secuencias

También se pueden declarar vectores mediante progresiones aritméticas y secuencias. La forma más común de expresar la progresión aritmética para crear un vector es seq(a, b, by = d) donde a es el inicio, b el final (no se puede superar este umbral, luego si el intervalo es tal que no se llega exactamente a b, el último valor es el inmediatamente anterior a b) y d es la diferencia que separa cada valor, el intervalo. Se pueden hacer tanto en orden creciente (a < b) como en orden decreciente (a > b). Además, se puede generar el vector con la longitud de la secuencia en lugar de con el tamaño del intervalo tal que seq(a, b, length.out=n) o, alternativamente, con el tamaño del intervalo y la longitud de la secuencia, en cuyo caso no hace falta especificar b, siendo entonces seq(a, by=d, length.out=n).

 [1]  5 10 15 20 25 30 35 40 45 50 55 60
[1]  5.0 12.5 20.0 27.5 35.0 42.5 50.0 57.5
[1] 100  90  80  70  60
 [1]  8.00000 14.22222 20.44444 26.66667 32.88889 39.11111 45.33333 51.55556
 [9] 57.77778 64.00000
 [1]  8 16 24 32 40 48 56 64 72 80

Por último, la forma escueta de hacerlo es a:b, que genera una secuencia en a y b con un intervalo del orden de unidades en números enteros entre los valores de a y b. Como detalle, si se hace -2:5 va de menos dos a más cinco, pero si se hace -(2:5) hará de menos dos a menos cinco.

 [1]  1  2  3  4  5  6  7  8  9 10
[1] -2 -1  0  1  2  3  4  5
[1] -2 -3 -4 -5

Funciones & Orden de Vectores

Hay veces en las que se puede aplicar directamente una función a un vector para transformar todos sus valores como elevar al cuadrado, multiplicar o hacer la raíz cuadrada pasando el vector como parámetro, pero hay funciones a las que no se le puede dar directamente x como argumento, sino que necesitan de algo más. Para aplicar una función a cada uno de los elementos de un vector sin recurrir a bucles se puede hacer con una sola instrucción, sapply(x, FUN = nombre_función). Por ejemplo, para hacer la raíz cuadrada se haría lo siguiente:

[1]  4.141593  5.141593  6.141593  7.141593  8.141593  9.141593 10.141593
[1]  2  4  6  8 10 12 14
[1]  1  4  9 16 25 36 49
[1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751
[1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751

Para tener un ejemplo más complejo, en la regresión lineal el coeficiente de determinación se podría calcular generando una función que sería la mostrada siguientemente y luego crear un vector z y hacer sapply(z, FUN = R).

[1] 0.9996613
[1] 0.9996613 0.9996613 0.9996613 0.9996613 0.9996613

También se tienen funciones básicas útiles en estadística para aplicar a vectores como length(x) para su obtener su longitud, max(x), que devuelve el máximo del vector, min(x), que calcula el mínimo, sum(x), que suma todos los elementos del vector, prod(x), que multiplica todos los elementos de un vector, mean(x), para definir la media del vector, diff(x), que calcula un vector a partir de las diferencias entre las entradas del vector original, y cumsum(x), que calcula el vector formado por las sumas acumuladas de las entradas del vector original.

 [1]  49  64  81 100 121 144 169 196 225 256 289 324
    Longitud       Máximo       Mínimo    Sumatorio     Producto        Media 
1.200000e+01 3.240000e+02 4.900000e+01 2.018000e+03 7.907097e+25 1.681667e+02 
 [1] 15 17 19 21 23 25 27 29 31 33 35
 [1]   49  113  194  294  415  559  728  924 1149 1405 1694 2018

Con la función sort(x) se ordenan los elementos del vector x según su orden creciente natural (numérico, alfabético…). Si se quiere un orden decreciente se ha de especificar con el parámetro decreasing = TRUE. Mientras, con rev(x) se invierten los elementos del vector.

 [1]  49  64  81 100 121 144 169 196 225 256 289 324
 [1] 324 289 256 225 196 169 144 121 100  81  64  49
 [1] 324 289 256 225 196 169 144 121 100  81  64  49

A través de cummax(x) se recorre un vector y se selecciona el elemento de mayor tamaño tal que el valor de la posición i+1 será el que tome i si i > i+1. Lo mismo sucede con cummin(x), pero con el valor mínimo. Finalmente, con cumprod(x) se consigue el vector del producto acumulado.

 [1]  49  64  81 100 121 144 169 196 225 256 289 324
 [1] 49 49 49 49 49 49 49 49 49 49 49 49
 [1] 4.900000e+01 3.136000e+03 2.540160e+05 2.540160e+07 3.073594e+09
 [6] 4.425975e+11 7.479897e+13 1.466060e+16 3.298635e+18 8.444505e+20
[11] 2.440462e+23 7.907097e+25

Para finalizar, como ya se ha dicho, mediante la función diff(x) se hace la diferencia entre i e i+1, de modo que con diff(cumsum(x)) se calculará la diferencia de la suma acumulada.

 [1]  64  81 100 121 144 169 196 225 256 289 324

Subvectores & Filtros

En ocasiones cuando se trabaja con vectores no se quiere utilizar todo el vector en sí, sino que se quiere extraer el valor de una determinada posición o un conjunto de sus valores. En este contexto aparecen los subvectores y los filtros. En R, los índices parte de 1, y si se hace x[i] se obtiene la i-ésima entrada del vector. Por el contrario, si se hace x[length(x)] se obtiene la última posición del vector. Para hacer splicing basta con escribir x[a:b] siendo a y b las posiciones en las que se quiere definir el subvector (se puede hacer también x[b:a] y da el vector invertido). Si se recurre a x[-i] se elimina el elemento i-ésimo del vector. También se puede hacer x[-c(1,2…,k)] y eliminar así las posiciones que se quiera (a diferencia de otros lenguajes, como el mismo Python, en R se comienza por uno, no por cero). Además, se pueden hacer operaciones aritméticas con subvectores mediante instrucciones como x[a:b]+3, que suma tres a todos los valores comprendidos entre las posiciones a y b.

[1] 196
[1]  64  81 100 121 144
[1] 196 169 144 121 100
 [1]  49  64  81 100 121 144 196 225 256 289 324
[1] 121 144 169 196 225 256 289 324
[1] 238 211 186 163 142

También se pueden poner condiciones lógicas para redefinir el vector como x[x!=a & x>b] de modo que se crea un subvector con los valores que cumplan dichas condiciones. Esto también se puede hacer con la función which(x condición). Otra función es which.min(x) que da la primera posición de un vector que toma el valor mínimo. Para obtener todas las posiciones que toman el valor mínimo se recurre a which(x == min(x)). Las mismas definiciones se aplican respectivamente a which.max(x) y which(x==max(x)) para el máximo.

[1] 196 225 256 324
[1] 1
[1] 1
[1] 12
[1] 12

Para obtener las posiciones pares se puede hacer x[seq(2, length(x), by=2)]; y para obtener las impares sería x[seq(1, length(x), by=2)]; y si se quieren eliminar se pondría simplemente un menos delante de seq() tal que x[-seq(2, length(x), by=2)]. Para obtener las cuatro últimas posiciones se haría x[(length(x)-3):length(x)]. Un detalle de interés es que al negar, por ejemplo, !x>10, lo que se está haciendo es pedir x<=10, esto es, se incluye el igual.

[1]  64 100 144 196 256 324
[1]  49  81 121 169 225 289
[1]  64 100 144 196 256 324
[1] 225 196 169

Con el objetivo de obtener los números pares de un vector se haría x[x %% 2 == 0], y los impares sería x[x %% 2 == 1].

[1]  64 100 144 196 256 324
[1]  49  81 121 169 225 289

Además, se puede filtrar un vector según los valores que tenga otro vector. Por ejemplo, seleccionar las posiciones de x cuyas posiciones análogas en y sean mayores que cuatro se haría x[y > 4].

[1] 2 1 8 3

Hay que resaltar, como en el caso previo, que las instrucciones which ofrecen posiciones, no el valor de la condición especificada. Para obtener los valores dados por which se haría x[which(x > 4)]. Si se pide con x[condición] un subvector que no contiene ningún elemento, es decir, la condición es tal que ningún valor del vector x la cumple, la consola devolverá numeric(0), que es la forma de expresar un vector numérico vacío.

[1] 5 8 9 7
[1] 2 1 8 3
[1] 3 7 2 4
[1] 9 8 7 7
[1] 4 2 1 8 3
[1] 4 2
numeric(0)

El Universo de los Valores Perdidos

Si se tiene un vector numérico de longitud x se puede añadir un nuevo valor al final haciendo x[length(x) + 1] = a, donde a es un número, y si hubiera sido x[length(x) + 6] = a, entonces las posiciones [x+1-x+5] hubieran sido rellenadas con valores perdidos, NA (Not Available), que es el símbolo que se utiliza en análisis de datos cuando se desconoce un valor.

 [1]  1  2  3  4  5  6  7  8  9 10 11 NA NA NA NA 16

Al haber NA, muchas funciones como sum(x), prod(x) o cumsum(x) entre otras no van a poder operar sobre el vector en cuestión y devolverán NA. Para evitar esto hay que utilizar el parámetro na.rm = TRUE. Por ejemplo, se haría sum(x, na.rm = TRUE) para sumar todos los valores del vector.

[1] NA
[1] 82
[1] NA
[1] 6.833333

El NA no es un valor en sí mismo. Por ello, al hacer which(x == NA) se obtendrá integer(0), ya que no es un valor, no se puede comparar con nada. Como consecuencia, para saber si hay NA en tu conjunto de datos existe una función específica, is.na(x). Para saber las posiciones en el vector en las que hay NA se hace which(is.na(x)). Hay ocasiones en las que conviene cambiar los NA por valores. Una técnica muy utilizada es sustituir los valores de NA por la media aritmética, para lo cual se hace x[is.na(x)] = mean(x, na.rm = TRUE).

integer(0)
[1] 12 13 14 15
 [1]  1.000000  2.000000  3.000000  4.000000  5.000000  6.000000  7.000000
 [8]  8.000000  9.000000 10.000000 11.000000  6.833333  6.833333  6.833333
[15]  6.833333 16.000000

También se pueden obviar los NA negándolos, como sería hacer sum(x[!is.na(x)]).

[1] 82
[1] 6.833333
[1] 18.33333

Esto anterior es de gran utilidad ya que hay funciones como cumsum(x), que no admiten el parámetro na.rm. Otra opción, pero no recomendada en estadística descriptiva, porque invita al engaño, es eliminar todos los NA mediante na.omit(x). Al hacer esto y guardarlo en una nueva variable, aparecen los atributos na.action, que marca sobre qué posiciones ha actuado la función na.omit(), y class, que indica el tipo de función, que es omit, de na.omit(), los cuales se pueden eliminar en caso de resultar molestos mediante attr(x, “na.action”) = NULL y attr(x, “class”) = NULL.

$na.action
[1] 12 13 14 15
attr(,"class")
[1] "omit"
NULL

Los Factores

Un factor tiene una estructura interna más rica que un vector que permite clasificar las observaciones. Los valores que puede tomar son los niveles o levels que, por defecto, siguen un orden alfabético. Así, cada nivel es un atributo del factor y cada elemento del factor es igual a un nivel. Para crear un factor primero se genera un vector tras lo cual se recurre a las funciones factor() o as.factor(). La diferencia es que as.factor() convierte el vector que ya se tiene en factores y toma como niveles los elementos que aparecen dentro del vector, mientras que factor() define un factor a partir del vector permitiendo definir los niveles (incluso da la oportunidad de añadir niveles que no aparecen en el vector) y sus etiquetas o nombres mediante los parámetros levels y labels respectivamente.

[1] Humano Orco   Orco   Humano Elfo   Humano Goblin Trasgo
Levels: Humano Orco Elfo Goblin Trasgo Troll
 [1] Hembra Macho  Hembra Hembra Hembra Macho  Hembra Macho  Macho  Hembra
[11] Hembra Macho  Hembra Hembra
Levels: Hembra Macho Intersexo
 [1] H M H H H M H M M H H M H H
Levels: H M

Para conocer los niveles de un factor se ofrece la función levels(), que además permite cambiar los nombres a las etiquetas e incluso añadir nuevos niveles. Cabe detallar que el orden de los niveles no se ve alterado y que es posible agrupar niveles mediante la asignación del mismo nombre a los niveles que se desean fusionar.

[1] "Humano" "Orco"   "Elfo"   "Goblin" "Trasgo" "Troll" 
[1] "Hembra"    "Macho"     "Intersexo"
[1] "Hembra"  "Macho"   "Híbrido"
 [1] Suspenso  Aprobado  Matrícula Aprobado  Suspenso  Suspenso  Aprobado 
 [8] Matrícula Aprobado  Suspenso  Aprobado  Aprobado  Aprobado 
Levels: Suspenso Aprobado Matrícula

Para terminar esta sección se tiene el factor ordenado, que como su nombre indica es aquel en el que los niveles siguen un determinado orden. Se declara mediante la función ordered(), que contiene los mismos parámetros que la función factor().

 [1] Suspenso  Aprobado  Matrícula Aprobado  Suspenso  Suspenso  Aprobado 
 [8] Matrícula Aprobado  Suspenso  Aprobado  Aprobado  Aprobado 
Levels: Suspenso < Aprobado < Matrícula

Las Listas

Las estructuras que se han visto hasta el momento presentan una clara restricción, y es que no pueden contener datos heterogéneos, esto es, de diferentes modos. Esto se resuelve con la lista, que es una estructura de datos formada por diferentes objetos, no necesariamente del mismo tipo, cada cual con un nombre interno. Muchas funciones en R devuelven una lista como resultado de su operación. Tal es el ejemplo de las funciones relacionadas con los modelos de regresión lineal, tal como lm(). Para crear una lista se recurre a la función list(). Se puede acceder a cada componente de la lista de dos modos que son o bien mediante el símbolo del dólar, es decir, lista$componente, o bien mediante los corchetes, a saber, lista[i]. Además, por medio de dobles corchetes se puede acceder directamente al objeto que contiene esa componente, pues las instrucciones previas devuelven la componente como un objeto de estructura lista de un solo elemento.

$variable
[1] "Temperatura"

$data
 [1] 32 30 34 31 32 32 33 34 36 34

$meanT
[1] 32.8

$suma
 [1]  32  62  96 127 159 191 224 258 294 328
 [1] 32 30 34 31 32 32 33 34 36 34
$data
 [1] 32 30 34 31 32 32 33 34 36 34
 [1] 32 30 34 31 32 32 33 34 36 34

Las funciones elementales para conocer la información de una lista son str(), que permite conocer la estructura interna de una lista, y names(), que da a saber los nombres de cada uno de los componentes de la lista.

List of 4
 $ variable: chr "Temperatura"
 $ data    : num [1:10] 32 30 34 31 32 32 33 34 36 34
 $ meanT   : num 32.8
 $ suma    : num [1:10] 32 62 96 127 159 191 224 258 294 328
[1] "variable" "data"     "meanT"    "suma"    

Si una función de R es aquello que fabrica la lista, como es el caso de la regresión lineal, la información devuelta es abundante y compleja, por lo general, de modo que estas funciones previas y otras importantes como summary() son críticas para entender los resultados.


Call:
lm(formula = y ~ x)

Residuals:
       1        2        3        4        5        6        7 
-0.09495 -0.92489 -0.38910  1.27440  0.03716  0.16000 -0.06263 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)   
(Intercept)  -0.1507     0.6846   -0.22  0.83444   
x             0.8122     0.1200    6.77  0.00107 **
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.7309 on 5 degrees of freedom
Multiple R-squared:  0.9016,    Adjusted R-squared:  0.882 
F-statistic: 45.84 on 1 and 5 DF,  p-value: 0.001068
(Intercept)           x 
 -0.1507287   0.8121779 
          1           2           3           4           5           6 
-0.09494787 -0.92488674 -0.38909745  1.27440129  0.03715570  0.16000360 
          7 
-0.06262854 
       1        2        3        4        5        6        7 
1.294948 3.024887 3.439097 2.805599 5.022844 5.899996 7.142629 

Las Matrices

Las matrices en R se generan mediante la función matrix(), que toma un vector como dato de entrada, y que consta de otra serie de parámetros como nrow, para indicar el número de filas de la matriz, ncol, para especificar el número de columnas (no es necesario si se utiliza nrow), y byrow, que toma un valor booleano y que sirve para decirle a la función si, con el vector suministrado, rellena la matriz fila a fila (TRUE) o columna a columna (FALSE), siendo este último el valor por defecto de este parámetro.

     [,1] [,2] [,3]
[1,]    1    5    9
[2,]    2    6   10
[3,]    3    7   11
[4,]    4    8   12
     [,1] [,2] [,3]
[1,]    1    2    3
[2,]    4    5    6
[3,]    7    8    9
[4,]   10   11   12

Si se especifican más columnas o filas de las que rellenaría el vector que se pasa como parámetro, R repetirá los elementos del vector en orden hasta completar la última posición.

     [,1] [,2]
[1,]    1    7
[2,]    2    8
[3,]    3    9
[4,]    4   10
[5,]    5   11
[6,]    6   12

Para crear una matriz solo de unos o solo de ceros no existe una función específica como sí sucedía en Python, sino que en lugar de pasar un vector a la función matrix(), se escribe solamente un número.

     [,1] [,2] [,3] [,4] [,5] [,6] [,7]
[1,]    1    1    1    1    1    1    1
[2,]    1    1    1    1    1    1    1
[3,]    1    1    1    1    1    1    1
[4,]    1    1    1    1    1    1    1
[5,]    1    1    1    1    1    1    1
     [,1] [,2] [,3] [,4] [,5] [,6] [,7]
[1,]    0    0    0    0    0    0    0
[2,]    0    0    0    0    0    0    0
[3,]    0    0    0    0    0    0    0
[4,]    0    0    0    0    0    0    0
[5,]    0    0    0    0    0    0    0

Con rbind() y cbind() se pueden añadir a las matrices creadas filas y columnas respectivamente. También estas funciones permiten fusionar vectores para la creación de matrices. Como requisito de estas funciones, los vectores utilizados han de tener la misma longitud. Además, se tiene una variante de estas instrucciones para la construcción de matrices diagonales, que es el comando diag(). En caso de que esta última función tome por valor un número, n, en lugar de un vector, generará una matriz escalar de orden n. 

     [,1] [,2]
[1,]    1    7
[2,]    2    8
[3,]    3    9
[4,]    4   10
[5,]    5   11
[6,]    6   12
[7,]    1    1
[8,]    0    0
     [,1] [,2] [,3] [,4]
[1,]    1    7   13   15
[2,]    2    8   14   16
[3,]    3    9   13   15
[4,]    4   10   14   16
[5,]    5   11   13   15
[6,]    6   12   14   16
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    0    0    0    0
[2,]    0    2    0    0    0
[3,]    0    0    3    0    0
[4,]    0    0    0    4    0
[5,]    0    0    0    0    5
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    0    0    0    0
[2,]    0    1    0    0    0
[3,]    0    0    1    0    0
[4,]    0    0    0    1    0
[5,]    0    0    0    0    1
     [,1] [,2] [,3] [,4] [,5]
[1,]    6    0    0    0    0
[2,]    0    6    0    0    0
[3,]    0    0    6    0    0
[4,]    0    0    0    6    0
[5,]    0    0    0    0    6

Para acceder a un elemento se utilizan los corchetes indicando la fila y la columna en este orden, matriz[1,j]. Si solamente se indica matriz[i,] se obtiene la fila i-ésima de la matriz; y por contra, si se hace matriz[,j], se define la columna j-ésima de la matriz. Además, si i y j son vectores de índices, se definirá una submatriz con las filas pertenecientes al vector i y las columnas dadas por el vector j.

[1] 9
[1]  2  6 10
[1]  9 10 11 12
     [,1] [,2]
[1,]    5    9
[2,]    7   11
[3,]    8   12

En lo que respecta a las funciones básicas que se pueden aplicar sobre las matrices en R, se tienen:

  • diag(matriz): para obtener la diagonal de la matriz.
  • nrow(matriz): devuelve el número de filas de la matriz.
  • ncol(matriz): devuelve el número de columnas de la matriz.
  • dim(matriz): informa sobre las dimensiones de la matriz.
  • sum(matriz): se obtiene el sumatorio de todas las entradas de la matriz.
  • prod(matriz): da el producto de todos los elementos de la matriz.
  • mean(matriz): devuelve la media aritmética de las entradas de la matriz.
     [,1] [,2] [,3] [,4]
[1,]    1    5    9   13
[2,]    2    6   10   14
[3,]    3    7   11   15
[4,]    4    8   12   16
[1]  1  6 11 16
[1] 4
[1] 4
[1] 136
[1] 2.092279e+13
[1] 8.5

Funciones más avanzadas pensadas para trabajar con matrices son las siguientes:

  • colSums(matriz): obtiene la suma de los elementos de cada columna.
  • rowSums(matriz): devuelve los sumatorios de los elementos por fila.
  • colMeans(matriz): define las medias aritméticas por columnas.
  • rowMeans(matriz): calcula las medias aritméticas por fila.
[1] 10 26 42 58
[1] 28 32 36 40
[1]  2.5  6.5 10.5 14.5
[1]  7  8  9 10

También se puede utilizar la función apply() para trabajar con los elementos de filas y columnas y aplicar funciones específicas más pulidas. Para esto la función apply() presenta el parámetro MARGIN, que toma el valor de uno si se trabaja con filas y habrá que pasarle un valor de dos para trabajar con columnas, siendo en añadido posible trabajar tanto con filas como con columnas poniendo ambos valores. Por ejemplo, para calcular el error cuadrático por fila y por columna, se haría:

[1] 16.61325 18.33030 20.09975 21.90890
[1]  5.477226 13.190906 21.118712 29.086079
     [,1] [,2] [,3] [,4]
[1,]    1    4    9   16
[2,]   25   36   49   64
[3,]   81  100  121  144
[4,]  169  196  225  256

Estructuras de Datos en Python

En este bloque se van a estudiar las mismas estructuras para Python que se han visto en R en la sección anterior.

Las Listas

En Python, la forma estándar de almacenar colecciones de objetos en un orden determinado es la lista, que son muy distintas a las de R. Estas listas equivalen a los arrays de R. Estas listas admiten objetos de diferente modo y permiten añadir o eliminar estos objetos de forma sencilla. Las listas se crean con corchetes y pueden estar vacías o incluir elementos de todo tipo, a saber:

[]
['Alex', 'Mark', 'Ilina']
[1, 2, 3, 5, 7, 11]
['A', 8, False, 'Alex', (4+6j)]

También se pueden crear listas por medio de operadores aritméticos, como se muestra a continuación.

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

A diferencia de R, el primer elemento de una lista tiene posición cero. Esta diferencia se debe al debate que existe entre los matemáticos en torno al número cero. Hay una rama de matemáticos que lo consideran un número natural y como tal lo incluyen como primer elemento de los números naturales, siendo esta perspectiva la que impera en Python, mientras que otra rama, los algebristas, que serían los dominantes en R, consideran que el uno es el primer número natural, mientras que el cero no sería un número natural, sino llanamente entero. Al respecto de este tema, recomiendo el vídeo siguiente del canal Derivando de Youtube. Para acceder a cada elemento basta con poner el nombre de la lista seguido de la posición o posiciones de los elementos de interés entre corchetes. Además de poder acceder a este elemento también se puede sobrescribir un valor cualquiera sobre él por medio del operador de asignación.

'A'
['A', 8, False]
[1, 2, 3, 4, 5, 6, 5, 0, 0, 0]
True

Si se quiere acceder a una posición que no existe o asignar a una posición que no existe un valor cualquiera, Python arrojará el siguiente error, que indica que el índice especificado se encuentra fuera del rango de índices de la lista. Si se tiene más de un elemento con tal valor, eliminará el primero de ellos.

Error in py_call_impl(callable, dots$args, dots$keywords): IndexError: list assignment index out of range

Detailed traceback: 
  File "<string>", line 1, in <module>

Se pueden adicionar y eliminar elementos de una lista. Para añadir elementos al final de la lista se tiene la función append, y para eliminar un elemento basta con usar la función remove, a la cual se pasa por parámetro el valor del elemento del que se quiere prescindir. Hay que tener en cuenta que si se repite el valor a eliminar, la función remove tan solo eliminará el que primero encuentre en la lista.

[1, 3, 4, 6, 5, 0, 0, 0, 8]

Con la función reverse se consigue girar o invertir la lista, y una función interesante es count, que sirve para realizar un conteo de las veces que aparece un elemento dado en la lista. Otra función de interés es index, que muestra el índice del valor dado como parámetro; y con la función len, que, a diferencia de las anteriores, no sigue la sintaxis del punto,

[8, 0, 0, 0, 5, 6, 4, 3, 1]
3
6
9

Para acceder a posiciones no contiguas se puede hacer con dos puntos seguidos indicando tras ello el número de posiciones que supone el salto. Si se quiere acceder a los últimos elementos de la lista, esto es, contando desde el final, se habrá de utilizar el menos delante de la posición siendo el último elemento en este caso el menos uno, no el cero.

[1, 3, 5, 7, 9]
[2, 4, 6, 8, 10]
10

Si se desea saber si un elemento se halla en la lista solamente se ha de utilizar el operador in.

True
False

Los Bucles

En Python los bucles cobran mayo importancia que en R al no tenerse las funciones apply. Hay dos tipos de bucles, que son los determinados y los indeterminados. Los primeros son los bucles for, que son aquellos que iteran sobre una colección, a saber, una lista, una tupla o una cadena o string entre otros.

1
2
3
4
5
6
7
8
9
10
10

Si se quieren hacer iteraciones sobre colecciones de rangos, se usa la función range.

0
1
2
3
4
2
3
4
5
6

También se puede usar para recorrer letra a letra una cadena de caracteres.

A
l
e
x

Las funcionalidades de los bucles van mucho más allá. Por ejemplo, se pueden utilizar para realizar cálculos, como puede ser el obtener un sumatorio.

8.143

El segundo tipo de bucle es el indeterminado, el bucle while, que se repite hasta que se quebranta una condición booleana. Por ejemplo:

0
1
2
3
4

Hay una variante en los bucles determinados que permite conocer tanto el valor como la posición de dicho valor. Para ello se usa un bucle for con la función enumerate.

Posición: 0. Valor: 2.
Posición: 1. Valor: 3.
Posición: 2. Valor: 5.
Posición: 3. Valor: 7.
Posición: 4. Valor: 11.

Si se quisiera acceder solamente a las posiciones bastaría con la siguiente construcción, que ejecuta un combo con las funciones range y len.

Posición: 0. Valor: 2.
Posición: 1. Valor: 3.
Posición: 2. Valor: 5.
Posición: 3. Valor: 7.
Posición: 4. Valor: 11.

Un ejemplo de algoritmo sencillo para trabajar con bucles es la Criba de Eratóstenes, que fue creado por el matemático Eratóstenes de Cirene, allá por el 200 a.C. Este algoritmo sirve para descubrir números primos. Parte del número dos, marca todos sus múltiplos y lo añade a la lista de primos, tras lo cual pasa al siguiente número entero, el tres. Se marcan todos los múltiplos de tres y se adiciona el tres a la lista de primos. Después se tiene el 4, pero éste, al ser múltiplo de dos ya está marcado por lo que no es primo y no entrará en la lista. El siguiente número es el cinco, que también es primo. Al igual que en los casos previos, se marcan todos sus múltiplos y se añade a la lista. Así pues, la lógica del algoritmo es que se va progresando número a número tal que, cuando se llega a un número que no está marcado, se descifran todos sus múltiplos y se añade a la lista de números primos.

[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]

Las Tuplas

La tupla es una estructura de datos de Python que no tiene equivalente en R. Consiste en una colección ordenada de objetos, como la lista, pero que, a diferencia de ésta, no se puede modificar. Esto implica que tiene tamaño y contenido fijo, por lo que el espacio que ocupa en memoria se conoce de antemano y permanecerá inalterado. En cuanto a su construcción, se recurre a paréntesis en lugar de a los corchetes que se utilizaban en las listas. Además, se permite al igual que en las listas construir colecciones heterogéneas.

(1, 2, 3, 4)
(1, 'Alex', True, (4+9j), 7.45)

El acceso a los elementos de las tuplas sigue la misma sintaxis que en las listas.

1
(1, 'Alex', True)
7.45

Una ventaja de las tuplas es que permiten la asignación múltiple, es decir, se pueden crear varias variables y asignarles elementos de una tupla con una sola instrucción, como se muestra en el siguiente bloque.

1
'Alex'
True
(4+9j)

Respecto a las funciones que permiten las tuplas son muy reducidas en comparación con las listas ya que no se permite en ellas manipulación alguna. Ahora bien, lo que se puede hacer es llevar a cabo la transformación de una tupla en una lista y a partir de ello realizar los cambios pertinentes. Para esto se tiene la función list. Y viceversa, esto es, se puede convertir una lista en una tupla por medio de la función tuple.

[1, 'Alex', True, (4+9j), 7.45]
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

Para finalizar este apartado, mediante la función split se pueden obtener trozos de una determinada estructura en base al criterio deseado. Por ejemplo, tomar una cadena de caracteres y separar en virtud del espacio en blanco. Esto creará una lista cuyos elementos serán cada una de las palabras.

['Inicio', 'del', 'Proyecto', 'PythoR']

Los Diccionarios

Un diccionario o un array asociativo es una colección no ordenada de pares de elementos clave-valor. Su indexación se establece a partir de las claves y no por la posición como sucedía en las listas y en las tuplas. En cuanto a la sintaxis para su declaración, en esta ocasión son las llaves, y cada para clave-valor se encuentra ligado por dos puntos. Por lo complejas que suelen ser estas estructuras, para facilitar su lectura, se recomienda poner cada para clave-valor en una línea de código. Con el siguiente ejemplo no se ve con claridad esta necesidad al ser muy simple, pero por lo general los pares clave-valor involucran a su vez estructuras como listas, tuplas e incluso otros diccionarios, que harían la lectura sumamente compleja en caso de escribirse seguido en una sola línea de código.

{'Milán': 7, 'Liverpool': 6, 'Real Madrid': 13, 'Bayern de Múnich': 5, 'Barcelona': 5}

Antes de continuar con los diccionarios, un elemento de utilidad para la función print es el operador %s, que permite agregar un valor a una cadena en Python. El %s significa que desea agregar un valor de cadena dentro de una cadena y esto puede ser una mera palabra o un conjunto de elementos como la lista de números primos obtenida en secciones previas. Este operador (%) se puede utilizar con otras configuraciones, como %d, para formatear diferentes tipos de valores enteros, o %f, para incluir valores flotantes, es decir, números reales. Con este último se puede indicar el número de decimales a mostrar mediante la sintaxis %.xf, donde x es el número de decimales a enseñar.

Hi, Álex
Los números primos hallados hasta el valor de 72 son:
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]
El dorsal de Toni Kross es 8
El terremoto ha sido de magnitud 3.7 en la Escala sismológica de Richter
Caballero, su nota es 8.57

En los diccionarios no existe el concepto de posición, sino que para acceder a los valores se recurre a la clave. Esto también permite sobrescribir un valor en caso de haber cometido un error. Lo mismo se aplica para añadir una nueva clave en el diccionario, esto es, simplemente se nombra entre corchetes la nueva clave y se le suministra un valor. Cabe observar que al llamar al diccionario el orden no se conserva porque viene dado por el orden alfabético de las claves y es un concepto irrelevante en los diccionarios.

7
{'Milán': 7, 'Liverpool': 6, 'Real Madrid': 13, 'Bayern de Múnich': 6, 'Barcelona': 5, 'Borussia Dortmund': 1}

Funciones interesantes con las que trabajar con diccionarios son keys, que sirve para conocer las claves del diccionario, y values, que accede a los valores del diccionario. También es aplicable la función len, que en este caso indica el número de pares clave-valor, y el operador in, que sirve para saber si una clave dada se encuentra en el diccionario.

dict_keys(['Milán', 'Liverpool', 'Real Madrid', 'Bayern de Múnich', 'Barcelona', 'Borussia Dortmund'])
dict_values([7, 6, 13, 6, 5, 1])
6
True
False
False

Para eliminar un par clave-valor basta con recurrir a la función del, a la cual se ha de suministrar el nombre de la clave de la que prescindir.

{'Milán': 7, 'Liverpool': 6, 'Real Madrid': 13, 'Bayern de Múnich': 6, 'Borussia Dortmund': 1}

Los Arrays & Numpy

La librería NumPy (numeric python) es el paradigma en lo que respecta a la manipulación y manejo de datos en Python. Esta librería proporciona funciones muy eficientes para realizar cálculos sobre matrices siempre que los datos sean homogéneos. Es una librería muy parecida a MATLAB. A la hora de importarla con la función import se suele escribir un acrónimo como np para facilitar la escritura de código más adelante. Otra forma de hacerlo es como se muestra a continuación a modo de comentario, con from, para no tener que usar el nombre de la librería cada vez que se llame a una de sus funciones. El asterisco significa que se importan todas las funciones de NumPy. Ahora bien, esta última forma de importar las funciones de las librerías no se encunetra aconsejada, ya que puede implicar conflicto entre librerías que contengan funciones con el mismo nombre.

Con NumPy se puede convertir en un array los objetos de una lista, como se enseña a continuación. Además, la función array tiene el parámetro dtype, que permite indicar el tipo de dato con el que se quiere trabajar en el array. Estos tipos de datos son bool_, que indica que los datos son de tipo booleano, a saber, ceros y unos, int_, que son los números enteros por defecto, intc, que es para números enteros según el tipo de dato del lenguaje C, intp, que es el número entero que se utiliza para la indexación, y luego están los int8, int16, int32 e int64, que son los enteros de ocho, dieciséis, treinta y dos y sesenta y cuatro bits respectivamente. Otros tipos de datos son uint, que es para enteros sin signo e incluye uint8, uint16, uint32 y uint64. Por otro lado se tienen los decimales, entre los que destaca float_, que son los decimales por defecto, encontrándose también float16, float32 y float64. El float16 tiene una precisión media, lo que significa un bit para el signo, cinco bits para el exponente y diez bits de mantisa; mientras que el float32 tiene precisión normal, que son un bit para el signo, ocho bits para el exponente y 23 bits para la mantisa; y float64 o double, que presenta un bit para el signo, once bits para el exponente y 52 bits para la mantisa, suponiendo entonces la precisión máxima. Por último, se tiene complex_, que sirve para especificar números complejos de 64 bits, y este tipo de dato tiene otras dos variantes que son complex64 y complex128, que son para 32 y 64 bits respectivamente, lo cual se debe a que la mitad de los bits van a la parte real y la otra mitad a la parte imaginaria.

array([1, 2, 3, 4, 5, 6, 7, 8])
array([1., 2., 3., 4., 5., 6., 7., 8.], dtype=float32)

Se puede inicializar un array solamente con ceros mediante la función zeros y para hacer lo mismo tan solo con uno se tiene la función ones. En ambas funciones primero se especifica el número de filas del array y tras ello el número de columnas.

array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])
array([[1., 1., 1.],
       [1., 1., 1.],
       [1., 1., 1.],
       [1., 1., 1.]])

A través de la función arange se pueden generar arrays cuyos valores se incrementen de forma regular. También permite introducir el tipo de dato con el parámetro dtype y el valor de separación de los valores en el intervalo.

array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
array([ 4,  5,  6,  7,  8,  9, 10])
array([ 7.,  8.,  9., 10., 11.])
array([ 8., 10., 12., 14., 16.], dtype=float32)

Una función parecida es linspace, que sirve para declarar un array con un número específico de elementos separado de forma equivalente entre el valor de inicio y el valor final.

array([1.        , 1.46666667, 1.93333333, 2.4       , 2.86666667,
       3.33333333, 3.8       , 4.26666667, 4.73333333, 5.2       ,
       5.66666667, 6.13333333, 6.6       , 7.06666667, 7.53333333,
       8.        ])

Para obtener la matriz identidad se recurre a la función eye, que toma por parámetro la dimensión.

array([[1., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0.],
       [0., 0., 1., 0., 0.],
       [0., 0., 0., 1., 0.],
       [0., 0., 0., 0., 1.]])

La forma de manipulación más básica de las matrices es redimensionarlas, para lo que se utiliza la función reshape. Otra función es ravel, que devuelve el array de forma unidimensional. Esta última se utiliza mucho en Inteligencia Artificial para aplanar vectores. Y una función parecida es flatten, que no aplana el array dado, sino que lo que hace es devolver una copia del array colapsado, es decir, no altera el array original a diferencia de ravel.

array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])
array([[0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0.]])
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])

Para realizar una transposición de arrays se tiene la función transpose y mediante resize se obtiene una copia del array con una forma específica, tal que, a diferencia de reshape, si no cuadran las dimensiones del nuevo array con aquel que se suministra a la función se añadirán valores repitiendo aquellos que se hallan en el array original.

array([[ 1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10]])
array([[ 1,  6],
       [ 2,  7],
       [ 3,  8],
       [ 4,  9],
       [ 5, 10]])
array([[ 1,  2,  3,  4,  5,  6],
       [ 7,  8,  9, 10,  1,  2],
       [ 3,  4,  5,  6,  7,  8]])

La Manipulación de los Arrays

Los arrays en NumPy incluyen una serie de propiedades intrínsecas para facilitar el trabajo de las funciones de esta librería. Por ejemplo, conocer el número de dimensiones, que recibe en Python el nombre de rango, se tiene la propiedad ndim, y para saber cuáles son estas dimensiones se encuentra la propiedad shape.

2
(3, 4)

Con la propiedad size se puede conocer el número de elementos total que incluye la matriz y mediante dtype se tendrá el tipo de dato que contiene la matriz.

12
dtype('int32')

La propiedad itemsize permite conocer el tamaño de cada uno de los elementos de la matriz en bytes y se complementa con data, que especifica la posición que esta matriz ocupa en memoria. Esto último puede interesar para informáticos que se concentren en bajo nivel, para un analista de datos es relativamente irrelevante.

4
<memory at 0x000000002C72A990>

Se accede a los elementos mediante corchetes, al igual que se hacía con los marcos de datos de R.

array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
array([ 8,  9, 10, 11])
11

Además, los atributos se pueden modificar. Para ello se llama a la propiedad y se utiliza el operador de asignación. Además, cuando se accede a subconjuntos con corchetes, cabe mencionar que el elemento del extremo derecho del intervalo está incluido, pero ha de recordarse que la indexación comienza en cero.

array([[ 0,  1,  2],
       [ 3,  4,  5],
       [ 6,  7,  8],
       [ 9, 10, 11]])
array([[3, 4],
       [6, 7]])

Si el array es lineal se puede acceder a elementos incluyendo saltos.

array([1, 2, 3, 4, 5, 6])
array([ 4,  6,  8, 10, 12, 14, 16])

Asimismo, estos saltos se pueden incluir en la creación del array. Por ejemplo:

array([20, 18, 16, 14, 12, 10])

Se puede utilizar uno de los arrays creados como instrucción de indexación de otro array.

array([20, 18, 16, 14, 12, 10])

Se permite también la indexación por operaciones relacionales. Por ejemplo, si se tiene un array de cincuenta elementos se puede realizar la indexación tal que sólo se muestren los valores menores o iguales que veinte.

array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
       17, 18, 19, 20])

Con estos accesos sencillos a los subconjuntos se pueden cambiar los valores seleccionados con el operador de asignación.

array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13,  7, 14, 28,
       56, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
       34, 35, 36, 37, 38,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8])

Los arrays creados son de tipo int64, de forma que al intentar asignar a una posición el valor de otro tipo de dato hay dos opciones que son que o bien se transformará al tipo de dato del array (por ejemplo, si se introduce un número real), o bien arrojará un error (por ejemplo, si se introduce un complejo). Esto obedece a la limitación de que los arrays han de ser homogéneos en lo que respecta al tipo de dato.

array([ 0,  1,  2,  3,  4,  5,  4,  7,  8,  9, 10, 11, 12, 13,  7, 14, 28,
       56, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
       34, 35, 36, 37, 38,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8])
Error in py_call_impl(callable, dots$args, dots$keywords): TypeError: can't convert complex to int

Detailed traceback: 
  File "<string>", line 1, in <module>

Introducción a la Presentación Gráfica

La Función Plot

En el paquete básico de R, stat, gran parte de la representación gráfica recae en las manos de la función plot. Existen librerías mucho más avanzadas como ggplot2, ggrepel, plotly o grid y sus derivadas para el diseño personalizado de gráficos, pero son para usuarios más avanzados. Con la función plot se pueden dibujar nubes de puntos con vectores x e y numéricos. Si sólo se da una de las variables o dimensiones, la función no lanzará ningún error, sino que el vector dado ocupará el eje de ordenadas y la frecuencia de los valores que incluya constituirá el eje de abscisas. Por ejemplo, con el marco de datos sobre los lirios (iris) que se tiene en R se puede pasar a esta función solamente la longitud del sépalo de los lirios, o comparar la longitud del sépalo con su ancho.

Frecuencias de las longitudes de Sépalo del marco Iris. Por Álex.

Frecuencias de las longitudes de Sépalo del marco Iris. Por Álex.

Ancho del Sépalo de las flores de lirio del marco Iris por su longitud de Sépalo. Por Álex.

Ancho del Sépalo de las flores de lirio del marco Iris por su longitud de Sépalo. Por Álex.


Cabe destacar que en ambos gráficos se ha incluido una leyenda en la parte inferior y se han centrado. Para hacer lo primero, en Markdown, se tiene la opción fig.caption, a la cual se suministra el texto que se quiera poner. Para hacer lo segundo se halla la opción de bloque fig.align entre las opciones de bloque, y se le pasa el valor center. Hay más parámetros relacionado con las figuras, entre los que se incluyen todos los relacionados con sus dimensiones como fig.dim, fig.width o fig.height, así como otros vinculados con la nitidez como fig.retina.

Se pueden crear también funciones y pasarse como parámetros a la función plot. Por ejemplo, relacionar la raíz del ancho del sépalo con su longitud o hacer otra función, \(f(x)\), que muestre la relación entre el cubo del ancho del sépalo y su longitud.

Raíz del Ancho del Sépalo de las flores de lirio del marco Iris por su longitud de Sépalo. Por Álex.

Raíz del Ancho del Sépalo de las flores de lirio del marco Iris por su longitud de Sépalo. Por Álex.

Ancho del Sépalo de las flores de lirio del marco Iris al cubo por su longitud de Sépalo. Por Álex.

Ancho del Sépalo de las flores de lirio del marco Iris al cubo por su longitud de Sépalo. Por Álex.

Los Parámetros de Plot. Volumen I

La función plot se puede modificar de modo sencillo por medio de sus muchos parámetros entre los que se incluyen las etiquetas de los ejes, ya vistas en la sección anterior, el título, el subtítulo o el color entre otros. Los parámetros más comunemente utilizados para una edición sencilla de los gráficos con esta función son los siguientes:

  • log: indica que se quiere un gráfico en escala logarítmica.
  • main: para escribir el título del gráfico. Si se desea escribir una expresión matemática en lugar de texto, se ha de utilizar la función expression.
  • xlab: permite escribir la etiqueta del eje X.
  • ylab: sirve para escribir la etiqueta del eje Y.
  • pch: se encuentra para elegir la forma del símbolo de los puntos de la nube. Por defecto toma el valor de uno y actualmente se dispone de hasta 25 símbolos.
  • cex: para especificar el tamaño de los símbolos.
  • col: permite elegir el color de los símbolos. El nombre de los colores se puede consultar en el siguiente enlace de la Universidad de Columbia.

Los valores que se pueden pasar al argumento pch son los que se muestran en la siguiente imagen.

Ahora se muestra un ejemplo del uso de estos parámetros con la longitud del pétalo de las flores del marco de datos iris. Se usa además la función par, que permite dividir el marco en varias casillas para incluir múltiples gráficos. En este caso se crea una grid con una fila y dos columnas, para lo que se recurre al argumento mfrow.

Como se puede deducir del bloque de código anterior, se puede utilizar una variable categórica para definir el valor de los símbolos y de los colores para la gráfica en lugar de pasarlos manualmente.

Un ejemplo interesante puede ser el de la Sucesión de Fibonacci, que puede expresarse como:

\[f_n=\frac{1}{\sqrt{5}}\left(\frac{1+\sqrt{5}}{2}\right)^n-\frac{1}{\sqrt{5}}\left(\frac{1-\sqrt{5}}{2}\right)^n\]

 [1]    1    1    2    3    5    8   13   21   34   55   89  144  233  377  610
[16]  987 1597 2584 4181 6765
Sucesión de Fibonacci. Por Álex

Sucesión de Fibonacci. Por Álex

En el gráfico anterior se ha incluido un nuevo parámetro, que es bg, el cual sirve para colorear los símbolos que tienen relleno, mientras que col para estos símbolos (21:25) se utiliza para dar color a los bordes.

A continuación, se muestra cómo se vería la gráfica si se pasa a escala logarítmica el eje de abscisas.

Sucesión de Fibonacci con escala logarítmica en el eje X. Por Álex

Sucesión de Fibonacci con escala logarítmica en el eje X. Por Álex

Y ahora lo que se dibujará al cambiar la escala logarítmica al eje de ordenadas. Como se observa, queda lineal.

Sucesión de Fibonacci con escala logarítmica en el eje Y. Por Álex

Sucesión de Fibonacci con escala logarítmica en el eje Y. Por Álex

Por último, se pueden pasar ambos ejes a escala logarítmica.

Sucesión de Fibonacci con escala logarítmica en los ejes X e Y. Por Álex

Sucesión de Fibonacci con escala logarítmica en los ejes X e Y. Por Álex

Los Parámetros de Plot Volumen II

Otro parámetro interesante de la función plot es type, que da forma a la gráfica pudiendo tomar los siguientes valores:

  • p: nube de puntos. Es el valor por defecto.
  • l: línea recta que une los puntos careciendo dichos puntos de símbolo.
  • b: línea recta que une los puntos teniendo dichos puntos de símbolo. Esta línea no traspasa los puntos.
  • o: línea recta que une los puntos teniendo dichos puntos de símbolo. En este caso la línea sí traspasan los puntos.
  • h: histograma de líneas.
  • s: histograma de escalones.
  • n: para no dibujar puntos. Por sí solo no tiene sentido este valor puesto que muestra una gráfica vacía.

Un ejemplo de la aplicación de todos estos tipos de gráficas sería el mostrado a continuación.

Más parámetros de la función plot serían los recogidos en la lista siguiente:

  • lty: permite especificar el tipo de línea. Puede tomar los valores:
    • solid (1): es la línea continua. Es el valor que toma por defecto.
    • dashed (2): es la línea discontinua.
    • dotted (3): línea de puntos.
    • dotdashed (4): línea alterna de puntos y rayas.
  • lwd: define el grosor de la línea.
  • xlim: modifica el rango del eje de abscisas. Recibe un vector de dos puntos, que son los extremos del intervalo.
  • ylim: altera el rango del eje de ordenadas. También recibe un vector de dos valores.
  • xaxp: cambia las posiciones de las marcas del eje de abscisas. Recibe una expresión que expresa cada cuánto se quiere una marca.
  • yaxp: transforma las posiciones de las marcas del eje de ordenadas.

Un ejemplo de aplicación de estos parámetros sería el siguiente:

Ejemplo de Aplicación de Múltiples Parámetros. Por Álex

Ejemplo de Aplicación de Múltiples Parámetros. Por Álex

Se puede tomar el ejemplo anterior y observar qué sucede al cambiar el parámetro lty.

Puntos & Rectas

Si en lugar de como se ha hecho anteriormente lo que se quiere es añadir varios objetos a un mismo gráfico con el core de R, se ha de ir añadiendo al gráfico creado la nueva representación gráfica a modo de capa. Hay que saber que al añadir una capa gráfica ya no se puede cambiar el diseño general, es decir, el rango de coordenadas de los ejes, las etiquetas o el título entre otros, son elementos que pertenecen al gráfico original, no se pueden modificar con las capas. Dentro de estas instrucciones que añaden capas a la función plot destacan points(x,y), que añade un punto de coordenadas x e y o una familia de puntos a un gráfico ya existente, y abline(), cuya función es añadir una recta al gráfico ya creado. En esta última hay casos especiales de interés, que son:

  • abline(a,b): se añade una recta del tipo \(y= ax + b\). Se usa con frecuencia para añadir rectas de regresión lineal calculadas por medio de distintas funciones de modelado.
  • abline(v = x0): con esta instrucción se añade una recta vertical, \(x = x_0 * v\).
  • abline(h = y0): mediante este parámetro se adiciona una recta horizontal, \(y = y_0 * h\).

Se puede recoger el gráfico creado en la última parte de la sección anterior para ejemplificar el uso de capas gráficas. En este caso se añade un punto azul en las coordenadas (20, -1) y una línea horizontal para \(y = -1\).

Añadir Puntos y Rectas. Por Álex

Añadir Puntos y Rectas. Por Álex

A continuación se muestra un ejemplo más completo para el manejo de capas gráficas.

Añadir Puntos y Rectas II. Por Álex

Añadir Puntos y Rectas II. Por Álex

Las funciones abline() suelen utilizarse para tener un formato de visualización de parrilla o cuadrícula (grid). Por ejemplo, para la siguiente parábola puede ser útil conocer puntos de corte.

Añadir Puntos y Rectas III. Por Álex

Añadir Puntos y Rectas III. Por Álex

Otro uso de abline() es la representación de asíntotas verticales y horizontales. Por ejemplo, si se dibuja la tangente con coordenadas desde menos pi hasta pi, se sabe que las asíntotas verticales se encuentran en menos pi medios y en pi medios puesto que las tangentes de 90º y 270º no están definidas y por tanto no existen en este punto, toman valor infinito.

Añadir Puntos y Rectas IV. Por Álex

Añadir Puntos y Rectas IV. Por Álex

Curvas & Textos

Existen más funciones para añadir información a un gráfico base ya dado. Una de estas funciones es text(), que sirve para añadir texto en un punto de cooredenadas x e y. Este texto se pasa a través del argumento labels de esta función. Incluye un parámetro adicional, pos, que permite indicar la posición del texto alrededor de las coordenadas x e y, y que toma un valor de entre los siguientes posibles: 1. Abajo. 2. Izquierda. 3. Arriba. 4. Derecha. 5. Sin especificar, lo que quiere expresar que el texto se ubica centrado en el punto \((x,y)\). En el gráfico mostrado a continuación las posiciones de los suspensos son hacia abajo, mientras que el resto se ponen encima del punto.

Añadir Texto a un Gráfico. Por Álex

Añadir Texto a un Gráfico. Por Álex

Además de texto se pueden añadir a los gráficos líneas o curvas. Para lo primero se dispone de la función lines(x,y), que añade a una gráfico existente una línea poligonal que une los puntos \(\left(x_i,y_i\right)\), siendo éstos vectores numéricos.

Añadir Rectas con la función lines. Por Álex

Añadir Rectas con la función lines. Por Álex

Para lo segundo se tiene la función curve(), que añade una curva a un gráfico ya creado. Requiere de poner el parámetro add como TRUE, ya que si no la curva no se añade, sino que se crea como una gráfica independiente. Esta curva se puede declarar mediante una expresión algebraica con una variable \(x\) o llamarse a través de su nombre si se hubiera definido con anterioridad.

Añadir Curvas a una Gráfica. Por Álex

Añadir Curvas a una Gráfica. Por Álex

Matplotlib

En Python existen múltiples librerías para la creación de gráficas. Para comenzar se recomienda el uso de Matplotlib, que es la librería básica que trae python y que trae de serie una sublibrería denominada PyPlot, que contiene una serie de funciones de estilizado que permiten que funcione como Octave o MATLAB. Otra librería, que es común a R y Python, es la ya mencionada ggplot2. Además, de forma reciente ha cobrado fama para la creación de gráficos interactivos la librería Plotly. Así, para escribir este documento, se carga el paquete reticulate y se instala maplotlib con la función py_install en caso de que no se hubiera hecho previamente.

Antes de comenzar a graficar, si se trabaja con un cuaderno de Jupyter, se ha de escribir %matplotlib inline, que ayuda a que se respete la forma en la que se presentan los gráficos. Una vez hecho esto, se importa la librería con la función import llamando a pyplot, y se crea un alias como plt para otorgar una sintaxis más clara a la llamada de las funciones de matplotlib.

Una vez cargada, basta con invocar la función plot para realizar el primer gráfico. Como se podrá observar, la gran diferencia es que por defecto no se grafica una nube de puntos, sino que se representa una recta con el vector de puntos suministrado. Por otro lado, y aquí sí que es igual que en R, si solamente se pasa un vector, el eje que éste ocupará es el de ordenadas, mientras que el eje de abscisas se verá rellenado con números autogenerados.

Si se añaden instrucciones en el mismo bloque de código se irán añadiendo éstas a modo de capas. Para finalizar y mostrar el conjunto de capas se tiene la función show.

Se muestra ahora cómo se haría en caso de tener dos vectores (cabe mencionar que lo que se están creando son listas). Estos vectores han de ser de la misma longitud tal que en caso de que no fuera así se obtendrá un error que enunciará que los vectores deben tener la misma primera dimensión.

Para que se vea la salida del error se añade un elemento más al vector \(y\).

Error in py_call_impl(callable, dots$args, dots$keywords): ValueError: x and y must have same first dimension, but have shapes (6,) and (7,)

Detailed traceback: 
  File "<string>", line 1, in <module>
  File "C:\Users\aleja\AppData\Local\R-MINI~1\envs\R-RETI~1\lib\site-packages\matplotlib\pyplot.py", line 2842, in plot
    **({"data": data} if data is not None else {}), **kwargs)
  File "C:\Users\aleja\AppData\Local\R-MINI~1\envs\R-RETI~1\lib\site-packages\matplotlib\axes\_axes.py", line 1743, in plot
    lines = [*self._get_lines(*args, data=data, **kwargs)]
  File "C:\Users\aleja\AppData\Local\R-MINI~1\envs\R-RETI~1\lib\site-packages\matplotlib\axes\_base.py", line 273, in __call__
    yield from self._plot_args(this, kwargs)
  File "C:\Users\aleja\AppData\Local\R-MINI~1\envs\R-RETI~1\lib\site-packages\matplotlib\axes\_base.py", line 399, in _plot_args
    raise ValueError(f"x and y must have same first dimension, but "

La función plot admite además que se le pase como parámetro un string que incluye como primera letra el color y como segunda el formato o tipo de gráfico. Por otra parte, para cambiar el rango de los ejes se encuentra la función axis, a la que se le suministra una lista con cuatro valores que van desde el extremo inferior del eje de abscisas hasta el extremo superior del eje de ordenadas.

(0.0, 15.0, 0.0, 60.0)

Se pueden dibujar varios gráficos con diferentes colores y formatos en una sola instrucción. En el caso siguiente se crea una secuencia de números con la función arange de Numpy y se dibujan distintas fórmulas sobre el gráfico. La primera es una recta \(y=x\) de color rojo y trazada con pequeños guiones; la segunda es una parábola \(y=x^2\) de color azul y con forma de cuadrados; y la última es una función cúbica \(y = x^3\) con un color verde y representada por medio de triángulos.

array([ 0. ,  0.2,  0.4,  0.6,  0.8,  1. ,  1.2,  1.4,  1.6,  1.8,  2. ,
        2.2,  2.4,  2.6,  2.8,  3. ,  3.2,  3.4,  3.6,  3.8,  4. ,  4.2,
        4.4,  4.6,  4.8,  5. ,  5.2,  5.4,  5.6,  5.8,  6. ,  6.2,  6.4,
        6.6,  6.8,  7. ,  7.2,  7.4,  7.6,  7.8,  8. ,  8.2,  8.4,  8.6,
        8.8,  9. ,  9.2,  9.4,  9.6,  9.8, 10. , 10.2, 10.4, 10.6, 10.8,
       11. , 11.2, 11.4, 11.6, 11.8, 12. , 12.2, 12.4, 12.6, 12.8, 13. ,
       13.2, 13.4, 13.6, 13.8, 14. , 14.2, 14.4, 14.6, 14.8, 15. , 15.2,
       15.4, 15.6, 15.8, 16. , 16.2, 16.4, 16.6, 16.8, 17. , 17.2, 17.4,
       17.6, 17.8, 18. , 18.2, 18.4, 18.6, 18.8, 19. , 19.2, 19.4, 19.6,
       19.8])
[<matplotlib.lines.Line2D object at 0x000000003B712CF8>, <matplotlib.lines.Line2D object at 0x000000003B712C50>, <matplotlib.lines.Line2D object at 0x000000003B712DD8>]

Hay otros parámetros como linewidth para definir el ancho de la línea.

Cabe mencionar que la línea dibujada por defecto tiene un imperceptible efecto difuminado que se puede observar si se hace zoom. Para obviar este valor por defecto primero se asigna a una variable (se pone una coma detrás del nombre de la variable ya que no solamente se exporta el gráfico, sino también el marco que lo contiene). Una vez hecho esto se llama al parámetro set_antialiased del gráfico y se le asigna el valor de False. Esto provoca que no aparezca el efecto difuminado pero sí que surjan los bordes de las líneas.

Una función más útil que la anterior es set_parameters o setp, que sirve para configurar una lista de propiedades múltiples para un conjunto de líneas tal que se aplica a diferentes capas de un mismo gráfico. Por ejemplo, con el gráfico del array data creado previamente se tendría.

[<matplotlib.lines.Line2D object at 0x0000000025EAE9E8>, <matplotlib.lines.Line2D object at 0x000000002C3E8630>]

Ahora se puede usar la instrucción setp y cambiar propiedades de ambas capas, globales, como el color o el grosor de la línea.

[None, None, None, None]

Otra forma de asignar estos parámetros globales es, no con una sintaxis de argumentos, sino como una lista con comillas tanto para los parámetros como para los valores como se hace en MATLAB.

[None, None, None, None]

Hay más parámetros como alpha, que se usa para jugar con la opacidad del gráfico siendo el valor de uno para asignar completamente opaco y un valor de cero para un trazo transparente. Además, aunque antes no se haya especificado, el estilo de la línea se asigna al parámetro linestyle, y el formato del punto al parámetro marker.

Por último, conviene mencionar que se ofrece el parámetro animated, que toma un valor booleano y permite animar la gráfica. Todos estos parámetros se pueden consultar con la función setp, pasando la variable que contiene el gráfico base.

  agg_filter: a filter function, which takes a (m, n, 3) float array and a dpi value, and returns a (m, n, 3) array
  alpha: float or None
  animated: bool
  antialiased or aa: bool
  clip_box: `.Bbox`
  clip_on: bool
  clip_path: Patch or (Path, Transform) or None
  color or c: color
  contains: unknown
  dash_capstyle: {'butt', 'round', 'projecting'}
  dash_joinstyle: {'miter', 'round', 'bevel'}
  dashes: sequence of floats (on/off ink in points) or (None, None)
  data: (2, N) array or two 1D arrays
  drawstyle or ds: {'default', 'steps', 'steps-pre', 'steps-mid', 'steps-post'}, default: 'default'
  figure: `.Figure`
  fillstyle: {'full', 'left', 'right', 'bottom', 'top', 'none'}
  gid: str
  in_layout: bool
  label: object
  linestyle or ls: {'-', '--', '-.', ':', '', (offset, on-off-seq), ...}
  linewidth or lw: float
  marker: marker style string, `~.path.Path` or `~.markers.MarkerStyle`
  markeredgecolor or mec: color
  markeredgewidth or mew: float
  markerfacecolor or mfc: color
  markerfacecoloralt or mfcalt: color
  markersize or ms: float
  markevery: None or int or (int, int) or slice or List[int] or float or (float, float) or List[bool]
  path_effects: `.AbstractPathEffect`
  picker: unknown
  pickradius: float
  rasterized: bool or None
  sketch_params: (scale: float, length: float, randomness: float)
  snap: bool or None
  solid_capstyle: {'butt', 'round', 'projecting'}
  solid_joinstyle: {'miter', 'round', 'bevel'}
  transform: `matplotlib.transforms.Transform`
  url: str
  visible: bool
  xdata: 1D array
  ydata: 1D array
  zorder: float

Gráficos Múltiples

La librería Numpy es complementaria tanto con Math como con Matplotlib, y permite trabajar con múltiples figuras y múltiples ejes. Las funciones de plot actúan sobre un determinado eje, el cual se puede consultar con la función gca (get current axis). También se puede acceder a la figura por completo mediante gcf (get current figure).

Para ver cómo trabajar con ejes primero se define la siguiente función.

Una vez hecho esto se crean un par de secuencias de valores con el comando arange para definir dos subgráficos.

Una vez se tienen los vectores y la función se combinan las funciones figure y subplot. El primer comando es opcional y sirve para identificar la figura. Con subplot se indica tanto las dimensiones del subgráfico como el identificador siendo sus parámetros ancho, alto e identificador en este orden. Estos parámetros se pueden escribir juntos, como se hacía en MATLAB, o separando por comas, sin seguir la herencia de MATLAB.

[<matplotlib.lines.Line2D object at 0x000000003B7575F8>, <matplotlib.lines.Line2D object at 0x000000003B7578D0>]

Otro ejemplo sería poner dos figuras en un mismo dibujo para lo cual se ejecutarían las instrucciones siguientes. Es importante reseñar que en Markdown es necesario, a diferencia de en Jupyter, utilizar la instrucción show dos veces para mostrar ambas figuras.

Figuras & Ejes

En Python, con Matplotlib, se tiene una dificultad añadida respecto a R en lo que respecta a las figuras y los ejes. La figura es el marco , la ventana que contiene la representación gráfica; y dentro de esta figura se hallan los ejes que definen el gráfico. Se puede contemplar como una doble ventana. La primera es una tabla rasa que lo contiene todo y posibilita la representación. La segunda ventana es lo que define y delimita el gráfico para que éste se comprenda, los ejes. Para evitar la representación por capas se encuentra el comando hold, que borra todo aquello que se ha trazado en un gráfico, permite sobrescribir. Además, la representación de múltiples subgráficos se define con subplot, no con una función que delimita la ventana de antemano como par() o layout() como se hacía en R.

Añadir Textos

Para añadir texto se va a utilizar de ejemplo el histograma de frecuencias, que es de utilidad para los análisis de variables cuantitativas. En este ejemplo se tiene una distribución normal de media cien y desviación típica de cincuenta con una muestra de diez mil sujetos.

Se puede acceder tanto al número de sujetos (n) como al número de barras del histograma o divisiones (bins) como al número de grupos definidos (patches).

Además, se puede estandarizar el histograma con el parámetro normed, que recientemente ha quedado obsoleto y se ha visto sustituido por density, como se puede verificar si se acude a la documentación. Si se quiere añadir texto se tiene la función text, a la que se pasa por parámetro las coordenadas de texto y aquello que se desea escribir. Para escribir el parámetro texto de esta función se pone como si se trabajara en el documento Markdown, en crudo, es decir, r (row), que marca la existencia de una cadena de texto, seguido de comillas y entre los símbolos de dólar, que indican el formato LaTeX, la oración o expresión matemática a escribir. Por último, se añade la cuadrícula o parrilla al gráfico con la instrucción grid tomando por valor True y para que ésta muestre los valores deseados como umbrales se han de ajustar con la ya mencionada función axis.

(-50.0, 250.0, 0.0, 0.01)

Por otra parte, puede ser de interés añadir flechas desde el texto anotado hasta aquella parte de la gráfica a la que se remite el texto. Para ello se va a crear una nueva distribución como ejemplo. Antes de ello, con la función figure se puede declarar de antemano las dimensiones del marco con figsize, que en este caso son de 10x6, y la resolución o número de píxeles por pulgada con dpi, al que se da el valor de noventa. Hecho esto se crea una gráfica con la función seno que va de cero a diez pi, y para señalar uno de los máximos locales de esta función, se utiliza la función annotate. En esta función se escribe primero el texto a mostrar, tras lo cual se enseñan las coordenadas x e y del punto a señalar seguidas de las coordenadas del texto a escribir, y por último, la forma de las flechas que señalan la anotación (arrowprops), que toma por valor un diccionario con las características de las flechas. Entre los parámetros de este diccionario sobresale shrink, que tiene como propósito indicar cómo de pronunciadas son las flechas, tal que cuando su valor es alto las flechas son más chatas, y cuanto más bajo más alargadas se mostrarán.

(-2.0, 2.0)

Las Escalas

Las escalas en los gráficos mostrados son por defecto lineales, pero esto también se puede manipular. Para este apartado se trabajará de nuevo con una distribución normal de media 0.5 y de desviación típica 0.3. Se filtrará para valores mayores de cero y menores que uno. Se recurrirá a la función sort para la ordenación del vector y tras el filtro, y se usará arange para crear en vector \(x\) con la misma longitud que el vector \(y\).

array([0.6471874 , 0.19588322, 0.69952519, ..., 0.23081144, 0.28477762,
       0.44652661])

Antes de realizar las gráficas se importa NullFormatter, que permite anular las divisiones de los ejes de las gráficas, lo cual será útil para una de las escalas a graficar.

A continuación, se dibujarán cuatro gráficos. El primero de los gráficos es el que enseña la escala lineal, que es la que toman las funciones xscale e yscale por defecto. El segundo gráfico representa la escala logarítmica para el eje de ordenadas. Se podría hacer para ambos ejes con las funciones mencionadas. En este caso se ha realizado una escala semilogarítmica. El tercer gráfico muestra una escala simétrica, que cobra sentido al representar \(x\) frente a \(y\) menos la media de \(y\), \(\bar{y}\). Para ello, la función yscale toma el valor de symlog, y se puede añadir el parámetro linthreshy para que muestre con mayor nitidez las líneas divisorias (actualmente se denomina linthresh). La forma, como se puede observar, es parecida a la de una función logística. El último de los cuatro gráficos es precisamente el de una curva logística, y para su obtención simplemente se ha pasar el valor logit a yscale. Esta escala muestra demasiadas divisiones en el eje Y. Para poder dar claridad al eje de ordenadas se invoca la función gca y se establece para el eje Y NullFormater. Como detalle, para que los valores de los ejes de los distintos gráficos no se superpongan, en Matplotlib existe una función denominada subplots_adjust, a la que se suministran los parámetros top, bottom, left y right para los márgenes, y hspace para el espacio vertical entre los subgráficos, y wsapce para su separación horizontal.

Estadística & Álgebra

La Estadística

Además de la ya explicada función summary para el cálculo de los estadísticos básicos, en R se encuentran más funciones en paquetes que no pertenecen al núcleo de R. Para utilizar estas funciones se instalan entonces los paquetes modeest, para la moda, raster, para obtener los cuantiles y el coeficiente de varianza, y moments, para calcular el coeficiente de asimetría y la curtosis.

Se cargan los tres paquetes instalados en el entorno.

Medidas de Tendencia Central o Centralización

Las medidas de tendencia central indican como de centrados o reunidos se hallan los datos. Así, se parte de un conjunto de datos \(X=\{x_1, x_2, ..., x_n\}\), que se expresa como una muestra de n elementos tal que \(|X|=n\). La primera de estas funciones es la media aritmética, que no es sino el sumatorio de todos los elementos dividido por la longitud del array.

\[\bar{x}=\frac{\sum_{i=1}^n{x_i}}{n}\]

Para su cálculo en R se tiene la función mean, de stats, y se puede verificar que de hecho calcula la media haciendo el sumatorio y dividiendo por la longitud. En los siguientes ejemplos se trabajará con la variable millas por galeón del marco de datos mtcars.

[1] 20.09062
[1] 20.09062

Otra medida de centralización es la mediana, que se puede explicar cómo el valor que se encuentra en el punto medio si se realiza una ordenación de los valores del conjunto \(X\), es decir, \(P(X\leq m) = 0.5\). Este valor central no tiene porqué ser único, sino que es posible hallar un par de valores como mediana, en cuyo caso se hace la media de estos dos valores. En R, para el cálculo de la mediana se tiene la función meadian, también de stats.

[1] 19.2

La tercera medida de dispersión es la moda, que es aquel valor que más se repite en el conjunto definido. Se expresa como \(p(X=M) \geq p(x = x_i)\ \ \forall{1\leq i \leq n}\). Esto quiere decir que la probabilidad de que \(X\) tome el valor de la moda es mayor o igual que el valor de la probabilidad de que \(X\) tome cualquier otro valor dentro del conjunto. En R se calcula mediante la función mfv (most frequent value) del paquete modeest.

[1] 10.4 15.2 19.2 21.0 21.4 22.8 30.4

Finalmente, se tienen los percentiles, que es un concepto que amplía la definición de la mediana a cualquier valor dentro de la distribución, esto es, \(P(X \leq x_p) = p \ \ p \in [0,1]\). Dentro de los percnetiles pueden ser de interés el percentil de cero, que es el mínimo, y el percentil de uno, que es el máximo; y para los diagramas de cajas o los diagramas de violines son importantes los percentiles de 0.25 y de 0.75, que son el primer y el tercer cuantil. Para obtenerlos se tiene la función quantile del paquete raster.

    0%    25%    50%    75%   100% 
10.400 15.425 19.200 22.800 33.900 

Medidas de Dispersión

La segunda familia de medidas son las de dispersión, que indican la concentración de los datos, su distribución. Las medidas de dispersión más conocidas son la varianza y la desviación típica, que son un modo de medir la distribución de los valores en torno a la media. Por la propia definición de la fórmula de la varianza, que se muestra a continuación, siempre se obtendrán valores positivos puesto que se trata de una suma de cuadrados.

\[s^2=\frac{\sum_{i=1}^n{(x_i-\bar{x})^2}}{n-1}\]

La desviación típica por su parte es la solución positiva de la raíz cuadrada de la varianza. Su existencia obedece a volver a la dimensión original de la variable sobre la que se calcula la varianza para que tenga sentido la interpretación de la medida de dispersión. De hecho, es la medida que se suele utilizar para el cálculo de los intervalos de confianza.

\[s = + \sqrt{s^2}\]

En R se calculan mediante las funciones var y sd de stats.

[1] 36.3241
[1] 6.026948

Por último, se encuentra el coeficiente de variación, que es una medida de dispersión que mide la variabilidad relativa entre la media y la desviación típica, como se puede deducir de su fórmula.

\[C_\nu = \frac{s}{\bar{x}} \cdot 100\]

Con esta medida se sabe si se ofrece mucha variabilidad, el impacto de esta variabilidad. Esto cobra sentido porque analizar la variabilidad solamente guiándose por la varianza se pueden alcanzar conclusiones erróneas sobre la distribución de los datos. Para el cálculo en R de esta medida se tiene la función cv de stats, y se puede comprobar calculando la desviación típica por cien y dividiendo por la media.

[1] 29.99881
[1] 29.99881

Medidas de Asimetría

El último grupo de medidas son las de simetría. Para entender estas medidas se ha de conocer qué es el momento de orden r de la media, que se concibe como el sumatorio de las diferencias de cada elemento del conjunto y la media elevado a r y este sumatorio es dividido por la longitud del conjunto. Esta fórmula es parecida a la varianza y la diferencia en el denominador (recuérdese que en la varianza se resta uno a la longitud del conjunto) obedece a que las muestras siempre tienen un grado menos que los estadísticos originales, es decir, se pierde un elemento al muestrear. Por ello, tanto en la varianza como en la desviación típica es preciso restar uno, mientras que para el concepto de momento no es necesario.

\[m_r = \frac{\sum_{i=1}^n{(x_i-\bar{x})^r}}{n}\]

Sobre esta definición se tienen dos medidas, que son el Coeficiente de Asimetría de Fisher y la Curtosis, que se definen respectivamente en base a los momentos de orden tres y cuatro. El Coeficiente de Fisher es el momento de orden tres dividido por la desviación típica al cubo.

\[CA_F = \frac{\sum_{i=1}^n{(x_i-\bar{x})^3}}{n \cdot s^3}\]

Mientras, la Curtosis es el momento de orden cuatro dividido por la desviación típica a la cuarta menos tres.

\[c = \frac{\sum_{i=1}^n{(x_i-\bar{x})^4}}{n \cdot s^4} - 3\]

En lo que respecta a la interpretación del Coeficiente de Fisher, existen tres posibilidades en cuanto a la definición de la distribución:

  • Simétrica: si el valor de Fisher es cero. Esto se observa para alguna distribución normal ideal, pero en la realidad es muy extraño encontrar este valor para el coeficiente de Fisher.
  • Asimétrica Positiva: para valores positivos del coeficiente de Fisher. La curva se encuentra desplazada hacia la izquierda. En otras palabras, la media es mayor que la mediana.
  • Asimétrica Negativa: para valores negativos del coeficiente de Fisher. La curva se halla desplazada hacia la derecha. La media es menor que la mediana en estos casos.

Para la interpretación de la Curtosis también se ofrecen tres posibilidades:

  • Mesocúrtica: si su valor es exactamente cero. También tiene la forma de una distribución normal.
  • Leptocúrtica: si es positiva. Los valores se concentran en el centro dando lugar a un pico más acusado. Apenas tiene colas.
  • Platicúrtica: si es negativa. Los valores apenas se concentran cerca de la media en pro de las colas. La curva queda achatada.

Para el cálculo de estas medidas en R se halla la función skewness del paquete moments, para el coeficiente de Fisher, y la función kurtosis, del mismo paquete, para la curtosis.

[1] 0.6404399
[1] 2.799467

En este caso, por los valores encontrados, se tiene una distribución de asimetría positiva y leptocúrtica. Esto se puede comprobar gráficamente a través de la función hist, que dibuja un histograma.

También uno se puede hacer una idea sobre la simetría de la distribución comparando la media con la mediana, como se ha explicado previamente. Para esta variable, la media es mayor que la mediana, lo que indica una distribución de asimetría positiva.

[1] 20.09062
[1] 19.2

Álgebra Lineal en R

En el campo del álgebra una función de gran importancia es la ya mencionada apply. De esta función es interesante trabajar con el parámetro MARGIN, que permite la selección de filas (1) y de columnas (2), tanto de forma aislada como conjunta. Se muestra ahora un ejemplo de una matriz 3x3 a la que se aplica una transformación de sus elementos al cuadrado y se obtienen los sumatorios por filas y por columnas.

     [,1] [,2] [,3]
[1,]    1    8    7
[2,]    5    5    8
[3,]    6    5    6
     [,1] [,2] [,3]
[1,]    1   64   49
[2,]   25   25   64
[3,]   36   25   36
[1] 16 18 17
[1] 12 18 21

Dentro del universo de las matrices existen una serie de operaciones interesantes, cuyas funciones en R son:

  • t(A): calcula la traspuesta de una matriz.
  • +: para la suma de matrices.
  • *: producto de un escalar por una matriz o de matrices elemento a elemento (producto vectorial de matrices).
  • %*%: producto de dos matrices. Se exige que el número de columnas de la primera sea igual al número de filas de la segunda. La matriz resultante tendrá tantas filas como la primera matriz del producto y tantas columnas como la segunda.
  • mtx.exp(A, n): sirve para elevar la matriz a n. Es una función del paquete Biodem y no calcula potencias exactas, sino que las aproxima.
  • %^%: para elevar matrices. Es dado por el paquete expm y al igual que el anterior no calcula potencias exactas, sino que las aproxima.
     [,1] [,2] [,3]
[1,]    1    5    6
[2,]    8    5    5
[3,]    7    8    6
     [,1] [,2] [,3]
[1,]    2   16   14
[2,]   10   10   16
[3,]   12   10   12
     [,1] [,2] [,3]
[1,]    1   64   49
[2,]   25   25   64
[3,]   36   25   36
     [,1] [,2] [,3]
[1,]  114  101   88
[2,]  101  114  103
[3,]   88  103   97
     [,1] [,2] [,3]
[1,] 1176 1644 1923
[2,] 1341 1764 2124
[3,] 1290 1641 2001
     [,1] [,2] [,3]
[1,] 1176 1644 1923
[2,] 1341 1764 2124
[3,] 1290 1641 2001

Y relacionado con las matrices y el algebra lineal se encuentran:

  • det(A): permite calcular el determinante de una matriz, que habrá de ser cuadrada.
  • qr(A)$rank: da el rango de la matriz, que es el número de columnas linealmente independientes. Al hacer qr se obtiene una lista con varios parámetros de la matriz entre los que se encuentran el rango, los pivotes o el atributo de la clase.
  • solve(A): obtiene la inversa de una matriz (siempre que no sea singular). También se puede usar esta función para resolver sistemas de ecuaciones lineales. Para esto último se hace solve(A, b), donde \(b\) es un vector
[1] 99
$qr
           [,1]       [,2]       [,3]
[1,] -7.8740079 -8.0010080 -10.541011
[2,]  0.6350006  7.0699272   5.892747
[3,]  0.7620008  0.8230343  -1.778379

$rank
[1] 3

$qraux
[1] 1.127000 1.567992 1.778379

$pivot
[1] 1 2 3

attr(,"class")
[1] "qr"
            [,1]       [,2]       [,3]
[1,] -0.10101010 -0.1313131  0.2929293
[2,]  0.18181818 -0.3636364  0.2727273
[3,] -0.05050505  0.4343434 -0.3535354

Si se multiplica una matriz por su traspuesta dará la identidad siempre y cuando la matriz no sea singular.

     [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    1    0
[3,]    0    0    1

Como se ha explicado, con solve se pueden resolver sistemas de ecuaciones lineales. Por ejemplo:

[1] -1.424242 -2.636364  4.787879

Para el cálculo de los vectores propios de un operador lineal, también denominados autovectores, que se definen como los vectores no nulos que, cuando son transformados por el operador, dan lugar a un múltiplo escalar de sí mismos, con lo que no cambian su dirección; se tiene la función eigen, la cual devuelve una lista con los valores propios y los vectores. Si se quiere acceder a los valores propios se usa el símbolo del dólar seguido de values y en caso de precisar de los autovectores se sigue la misma sintaxis con vectors.

eigen() decomposition
$values
[1] 17.081327 -3.352555 -1.728772

$vectors
           [,1]        [,2]       [,3]
[1,] -0.5511728 -0.83472935 -0.5549506
[2,] -0.6071226 -0.02709366 -0.4324976
[3,] -0.5723728  0.54999350  0.7106164
[1] 17.081327 -3.352555 -1.728772
           [,1]        [,2]       [,3]
[1,] -0.5511728 -0.83472935 -0.5549506
[2,] -0.6071226 -0.02709366 -0.4324976
[3,] -0.5723728  0.54999350  0.7106164

Para realizar la descomposición canónica de una matriz se ha de cumplir que la matriz dada ha de ser igual al producto de los vectores propios de la matriz por la diagonal de la matriz cuyas entradas son los valores propios de la matriz A por la inversa de la matriz de los autovectores.

\[A = P \cdot D \cdot P^{-1}\]

     [,1] [,2] [,3]
[1,] TRUE TRUE TRUE
[2,] TRUE TRUE TRUE
[3,] TRUE TRUE TRUE

Como últimos detalles a destacar, si existe un valor propio con multiplicidad algebraica mayor que uno, esto es, que aparece en más de una ocasión, la función eigen dará tanto valores a este valor propio como su multiplicidad algebraica indique. Sin embrago, R buscará que los vectores propios asociados a cada uno de los valores propios sean linealmente independientes. Entonces, si se obtiene como resultado autovectores repetidos asociados a un valor propio de multiplicidad algebraica mayor que uno, es porque para este valor propio no existen tantos vectores propios linealmente independientes como su multiplicidad algebraica, es decir, su multiplicidad geométrica no es igual a su multiplicidad algebraica. Por consiguiente, esto viene a expresar que la matriz no es diagonalizable. Se enseña un ejemplo de este caso a continuación:

eigen() decomposition
$values
[1] 3 2 2

$vectors
           [,1]       [,2]       [,3]
[1,] -0.1301889 -0.1825742 -0.1825742
[2,] -0.3905667 -0.3651484 -0.3651484
[3,]  0.9113224  0.9128709  0.9128709

Todas las operaciones algebraicas para matrices vistas en esta sección son aplicables para matrices de números complejos, a saber:

     [,1] [,2]
[1,] 2-2i 5+3i
[2,] 1+2i 2-1i
     [,1]  [,2]
[1,] 4-4i 10+6i
[2,] 2+4i  4-2i
      [,1]  [,2]
[1,] -1+5i 29-3i
[2,] 10+5i  2+9i
     [,1] [,2]
[1,] 2-2i 1+2i
[2,] 5+3i 2-1i
                       [,1]                 [,2]
[1,] 0.06756757+0.09459459i 0.1135135-0.2810811i
[2,] 0.09459459-0.06756757i 0.1189189+0.0864865i
[1] 1.475676-1.654054i 1.545946+0.124324i
eigen() decomposition
$values
[1]  4.430016+1.174879i -0.430016-4.174879i

$vectors
                     [,1]                  [,2]
[1,] 0.8247462+0.0000000i  0.8727730+0.0000000i
[2,] 0.5257689+0.2082326i -0.4793762-0.0920095i

La única excepción es la función det para el cálculo del determinante de la matriz, que no está definido para números complejos.

Error in determinant.matrix(x, logarithm = TRUE, ...): 'determinant' not currently defined for complex matrices

Ahora bien, existe un truco para el cálculo del determinante y es por medio del producto de los autovalores.

[1] 3-19i

El Poder de Reticulate

Como se explicó al comienzo del libro, Python y R no son excluyentes, sino complementarios, y cada vez más, como se puede comprobar con los planes de actualización de RStudio o con el entorno de desarrollo recientemente diseñado, prython. Esto obedece a que cada uno tiene sus fortalezas y debilidades. Por ejemplo, hay librerías exclusivas de Python como Tensorflow, mientras que en R se encuentran paquetes especializados y optimizados para el manejo de datos como Tidyverse. En este contexto surge Reticulate para el diseño de informes que combinen ambos lenguajes en Markdown.

Una forma de trabajar con reticulate es desde los propios bloques de R. Por ejemplo, se puede importar la librería os de Python y guardarla como una variable de R con el comando import de reticulate. Así, se puede invocar la librería os desde R por medio de la sintaxis de dólar.

 [1] "apuntes"                                  
 [2] "cheatsheetsMaster"                        
 [3] "data"                                     
 [4] "datasciencebook.pdf"                      
 [5] "Diploma.pdf"                              
 [6] "IntroductionML.Rmd"                       
 [7] "IntroductionML_files"                     
 [8] "LibroProbabilidadUdemy.pdf"               
 [9] "logos"                                    
[10] "LordRings.html"                           
[11] "LordRings.Rmd"                            
[12] "objetosprescindibles"                     
[13] "ParaPdf.txt"                              
[14] "PythoR.html"                              
[15] "PythoR.pdf"                               
[16] "PythoR.Rmd"                               
[17] "PythoR.tex"                               
[18] "PythoR_files"                             
[19] "scripts"                                  
[20] "wordcloud1.png"                           
[21] "wordcloud2.png"                           
[22] "wordcloud3.png"                           
[23] "~$rso Machine Learning en R y Python.docx"

Se puede indicar qué versión de Python utilizar mediante la función use_python, indicando la ruta donde se tiene dicha versión en el ordenador. Además de esto, se puede instalar un paquete directamente desde reticulate a través de la función py_install como se mostró en capítulos anteriores para la instalación de matplotlib.

Dicho esto, cuando se pase de un lenguaje a otro los tipos de datos son convertidos de forma automática. Séase, los vectores de un solo elemento de R se convierten en escalares en Python; los vectores de múltiples elementos se transforman en listas; las listas de múltiples tipos de R son tuplas en Python; las namelist de R en Python son diccionarios; las matrices y arrays de R se convierten en objetos de Numpy; los marcos de datos de R son marcos de datos de Pandas, y las funciones y tipos booleanos se conservan. Hay que tener en cuenta que Python es un lenguaje orientado a objetos, y en R la mayoría de objetos son referenciados, lo que exige ciertas modificaciones.

Una función añadida es source_python, que permite cargar un script de Python en R y utilizar así sus objetos. Por ejemplo, se tiene el siguiente script con la función cuadrado. Se carga el script y se invoca la función de este script. Este concepto se ve ampliado con py_run_file, que permite ejecutar el programa principal de una clase que se haya desarrollado en un archivo trabajado en Python.

[1] 16

Siguiendo con el estudio de las funciones de reticulate, se puede importar la librería Numpy y evitar que convierta los tipos de datos de python a R con el parámetro convert de la función import. Esto permite trabajar en R con objetos nativos de la librería Numpy.

[ 1  3  6 10 15 21]

Para efectuar una conversión explícita de estructuras de datos desde Python hacia R se halla py_to_r. Esto hace cómodo el trabajar con ambos lenguajes de forma coordinada.

[1]  1  3  6 10 15 21

Una función importante es aquella que permite obtener ayuda de funciones de python. Su nombre es py_help y basta con suministrarle el nombre de la función de la librería de python a la que pertenece.

Un ejemplo puede ser crear un array desde R con la función np_array de reticulate. El parámetro order igual a \(C\) sirve para especificar el guardado del objeto como si se tratara de lenguaje C en lugar de Fortran.

[ 1  2  3  4  5  6  7  8  9 10]

Lo mismo sucede con los marcos de datos. La excepción aquí es que en R hay factors mientras que en Python lo que se tiene son categorías. Otra diferencia con la que se ha de tener cuidado son las variables que se remiten a fechas. Se mostrará ahora como pasar de un marco de datos de R como es iris a Python.

  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa

Una vez aplicada la función r_to_py se puede trabajar con este marco de datos en bloques de python. Para esto se utiliza la sintaxis del punto poniendo r seguido del nombre del objeto, que en este ejemplo es el marco de datos guardado en la variable liriospy, y se ejecutan las funciones de Python que se quiera. Como se podrá observar ahora la indexación comienza en cero.

   Sepal.Length  Sepal.Width  Petal.Length  Petal.Width Species
0           5.1          3.5           1.4          0.2  setosa
1           4.9          3.0           1.4          0.2  setosa
2           4.7          3.2           1.3          0.2  setosa
3           4.6          3.1           1.5          0.2  setosa
4           5.0          3.6           1.4          0.2  setosa

Como último ejemplo se tiene las matrices sparse o dispersas, también conocidas como matrices ralas o huecas, que son aquellas matrices de gran tamaño cuyos elementos son en su mayoría ceros. Se usan mucho para los modelos de Markov y procesos estocásticos. En R se crean mediante la función sparseMatrix del paquete Matrix. En este caso se creará una matriz sparse 8x8 y se transformará a de un objeto de R a uno de Python.

8 x 8 sparse Matrix of class "dgCMatrix"
                                                                          
[1,] .         .         .         0.6445426 .         .         .        
[2,] .         .         .         .         .         0.4537281 .        
[3,] .         0.9516588 .         .         .         .         .        
[4,] .         .         0.3267524 .         .         .         .        
[5,] .         .         .         .         .         .         0.9654153
[6,] .         .         .         .         .         .         .        
[7,] 0.3898285 .         .         .         .         .         .        
[8,] .         .         .         .         0.7074819 .         .        
              
[1,] .        
[2,] .        
[3,] .        
[4,] .        
[5,] .        
[6,] 0.1789636
[7,] .        
[8,] .        

Se expresa ahora la matriz sparse en un bloque de Python.

array([[0.        , 0.        , 0.        , 0.64454264, 0.        ,
        0.        , 0.        , 0.        ],
       [0.        , 0.        , 0.        , 0.        , 0.        ,
        0.45372807, 0.        , 0.        ],
       [0.        , 0.95165875, 0.        , 0.        , 0.        ,
        0.        , 0.        , 0.        ],
       [0.        , 0.        , 0.32675241, 0.        , 0.        ,
        0.        , 0.        , 0.        ],
       [0.        , 0.        , 0.        , 0.        , 0.        ,
        0.        , 0.96541532, 0.        ],
       [0.        , 0.        , 0.        , 0.        , 0.        ,
        0.        , 0.        , 0.17896358],
       [0.38982848, 0.        , 0.        , 0.        , 0.        ,
        0.        , 0.        , 0.        ],
       [0.        , 0.        , 0.        , 0.        , 0.70748188,
        0.        , 0.        , 0.        ]])

Además, se puede volver a transformar el objeto en una estructura de R. Esto cobra sentido después de realizar la manipulación del objeto con estructura de Python con las funciones de numpy o pandas entre otras librerías. Para hacer esta transformación se halla la función py_to_r.

          [,1]      [,2]      [,3]      [,4]      [,5]      [,6]      [,7]
[1,] 0.0000000 0.0000000 0.0000000 0.6445426 0.0000000 0.0000000 0.0000000
[2,] 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.4537281 0.0000000
[3,] 0.0000000 0.9516588 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000
[4,] 0.0000000 0.0000000 0.3267524 0.0000000 0.0000000 0.0000000 0.0000000
[5,] 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.9654153
[6,] 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000
[7,] 0.3898285 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000
[8,] 0.0000000 0.0000000 0.0000000 0.0000000 0.7074819 0.0000000 0.0000000
          [,8]
[1,] 0.0000000
[2,] 0.0000000
[3,] 0.0000000
[4,] 0.0000000
[5,] 0.0000000
[6,] 0.1789636
[7,] 0.0000000
[8,] 0.0000000

Caso Práctico de Estadística Descriptiva

Para estudiar ejemplos de análisis de datos una excelente herramienta es Kaggle, una página web que recoge multitud de análisis y marcos de datos para su estudio. En este caso práctico se va a mirar un análisis de datos del libro de El Señor de los Anillos de Xavier Vivancos García, quien tiene también un análisis interesante de Star Wars. Este estudio se engloba en el marco del Procesamiento del Lenguaje Natural (NLP, Natural Language Processing). Además, como recursos adicionales para la minería de texto y el análisis de sentimientos se tienen:

Carga de los Marcos de Datos

En este caso práctico se trabajará con dos marcos de datos relacionados con El Señor de los Anillos. Uno de ellos contiene información sobre el guión de las películas y el otro trata sobre las características de los personajes, a saber, sexo, raza y estatura entre otros. Por otra parte, se requieren de múltiples paquetes para la manipulación y representación de los datos. Con algunos de estos paquetes ya se ha trabajado previamente a lo largo del libro, pero se volverán a cargar por si el usuario quiere replicar el archivo de forma independiente. Se tienen tidyverse, para la manipulación de datos, tm, para trabajar en minería de texto, wordcloud y wordcloud2, para la génesis de nubes de palabras, Tidytext, para el procesamiento de palabras y análisis de sentimientos, reshape2, que sirve para moldear la forma de los marcos de datos, Rweka, que se utiliza para tareas de minería de datos, knitr, para generar archivos dinámicos, gridExtra, que tiene funciones diversas para trabajar con las casillas en las que se dibujan los gráficos, grid, que permite añadir la casilla a los gráficos, magick, que se usa para el procesamiento avanzado de imágenes (también existe en forma de aplicación, por si se quieren consultar sus funciones o trabajar con una interfaz), memery, que se usa para la generación de memes, ggimage, que permite visualizar imágenes y objetos gráficos en gráficos de ggplot2, e igraph y ggraph, que sirven para la creación y manipulación de grafos o redes.

A continuación, se carga el marco de datos sobre los diálogos de los personajes y los sistemas de calificación de los lexicones. Existen tres grandes sistemas de puntuación basados en lexicones de unigramas, esto es, de palabras únicas, que se pueden consultar en el libro Text Mining with R, escrito por Julia Silge y David Robinson. Estos sistemas son:

  • AFINN, de Finn Årup Nielsen, que establece una escala que va de -5 a +5, tal que los valores negativos indican sentimientos negativos y los positivos sentimientos positivos.
  • bing, de Bing Liu et al., que categoriza las palabras en las categorías de positivo o negativo de modo binario.
  • nrc, de Saif Mohammad y Peter Turney, que clasifica de forma binaria las palabras, es decir, como sí o no, en los sentimientos positivo, negativo, ira, ilusión, asco, miedo, alegría, tristeza, sorpresa y confianza.

Con el paquete Tidytext se pueden utilizar directamente estos sistemas de categorización. A pesar de ello, se cargan en este caso como ficheros .csv.

Ahora se procede a visualizar el marco de los diálogos de forma superficial para conocer su estructura, para lo cual se emplean las funciones head, tail y str.

X char dialog movie
0 DEAGOL Oh Smeagol Ive got one! , Ive got a fish Smeagol, Smeagol! The Return of the King
1 SMEAGOL Pull it in! Go on, go on, go on, pull it in! Â The Return of the King
2 DEAGOL Arrghh! The Return of the King
3 SMEAGOL Deagol! Â The Return of the King
4 SMEAGOL Deagol! Â The Return of the King
5 SMEAGOL Deagol! Â The Return of the King
X char dialog movie
2385 2384 GANDALF Run, Shadowfax show us the meaning of haste. The Return of the King
2386 2385 PIPPIN Merry! The Return of the King
2387 2386 ARAGORN Merry! The Return of the King
2388 2387 MERRY He’s always followed me everywhere I went since before we were tweens. I would get him into the worst sort of trouble but I was always there to get him out. Now he’s gone. , Just like Frodo and Sam. The Return of the King
2389 2388 ARAGORN One thing I’ve learnt about Hobbits: They are a most hardy folk. The Return of the King
2390 2389 MERRY Foolhardy maybe. He’s a Took! The Return of the King
'data.frame':   2390 obs. of  4 variables:
 $ X     : int  0 1 2 3 4 5 6 7 8 9 ...
 $ char  : chr  "DEAGOL" "SMEAGOL" "DEAGOL" "SMEAGOL" ...
 $ dialog: chr  "Oh Smeagol Ive got one! , Ive got a fish Smeagol, Smeagol!    " "Pull it in! Go on, go on, go on, pull it in!  " "Arrghh! " "Deagol!  " ...
 $ movie : chr  "The Return of the King " "The Return of the King " "The Return of the King " "The Return of the King " ...

Se efectúa un resumen estadístico de las variables.

X char dialog movie
Min. : 0.0 Length:2390 Length:2390 Length:2390
1st Qu.: 597.2 Class :character Class :character Class :character
Median :1194.5 Mode :character Mode :character Mode :character
Mean :1194.5 NA NA NA
3rd Qu.:1791.8 NA NA NA
Max. :2389.0 NA NA NA

Funciones de Minería

En este apartado se definirán las principales funciones para llevar a cabo tareas de minería. La primera función a construir sirve para la limpieza y procesado del marco para crear el corpus, y consta de las siguientes funciones:

  • removePunctuation(): elimina todos los símbolos de puntuación.
  • stripWhitespace(): elimina los espacios en blanco restantes.
  • tolower(): lleva todo el texto a minúsculas.
  • removeWords(): elimina las palabras de uso común en inglés que no sirven para el análisis de sentimientos (stop words).
  • removeNumbers(): elimina los números.

La segunda función a construir sirve para crear una matriz con los unigramas, que describirá la frecuencia de cada palabra en el texto. Esta matriz tendrá los términos en la primera columna y los documentos en la parte superior como nombres de columnas individuales. Además, se ordenan por frecuencia decreciente con la función sort() y se transforma finalmente la matriz en una marco de datos.

Las siguientes funciones a elaborar sirven para la extracción de bigramas (lexicones de dos palabras) y trigramas (lexicones de tres palabras) mediante la función NGramTokenizer() del paquete RWeka.

Se describe la misma función para obtener el marco de datos sobre la frecuencia de las palabras que se hizo para los unigramas, pero ahora tanto para bigramas como para trigramas, en dos funciones independientes.

Análisis de Datos

Una vez definidas las funciones se pasa a llevar a cabo el análisis de datos. En primer lugar, se va a mirar qué personajes tienen más líneas de diálogo a lo largo de la trilogía, para lo cual se utilizará un diagrama de barras de ggplot2. Primero se toma el marco de datos, se realiza el conteo de personajes, se ordenan en orden decreciente y se realiza un corte con slice() para quedarse con los 22 cuya participación es más alta. Después se realiza el diagrama de barras con la instrucción geom_bar() y se voltea con el comando coord_flip() para que las barras queden horizontales. Después se añade una imagen de Gollum, que se carga con la función image_read() del paquete magick, con el comando grid_raster().

Como se puede observar, Frodo y Sam son los personajes con más líneas de diálogo, lo cual tiene sentido si se ven las películas, pues sus partes son aquellas que implican menos escenas de acción. También se pueden utilizar los valores relativos en lugar de los absolutos para realizar la comparación respecto al total de líneas de diálogo. Para ello basta con replicar el bloque previo añadiendo una columna al marco de datos que calcule el valor relativo para cada personaje, para lo que se emplea la función mutate(), y para poner las etiquetas se recurre a la función scale_y_continuous y se especifica en el parámetro labels que se exprese en porcentaje.

Además, se puede comparar la participación de cada personaje por película. De nuevo, se realiza la criba por aquellos personajes que presenten más líneas de diálogo. En este caso se tiene tanto un diagrama de barras como un gráfico de pastel.

Como se puede deducir de los dos gráficos anteriores, la película El Señor de los Anillos: Las Dos Torres es la que presenta más diálogos, lo cual es un elemento más que confirma, sin lugar a dudas, que es la mejor película de la trilogía, algo compartido por todas las personas de cultura.

Análisis de Sentimientos

Una vez analizados los diálogos por personaje y por película es hora de adentrarse en el análisis de sentimientos. En primer lugar, se recurre a la función unnes_tokens(), que permite la transformación del marco de datos en una estructura de datos limpia o tidy, que consta de un solo token por línea.

Una vez se ha llevado a cabo la limpieza se puede porceder a realizar la representación gráfica. El primer sistema de categorización a utilizar es el bing de Bing Liu et al.. Para representar la nube de palabras se recurre al comando comparison.cloud().

El siguiente sistema es el nrc de Saif Mohammad & Peter Turney, y sirve para descubrir qué sentimiento de entre los que recoge como categorías fue el predominante en las películas.

Como se puede deducir del diagrama de barras previo, predominan los sentimientos positivos, seguidos de cerca por los negativos, y en un tercer peldaño se hallan a la par el miedo, la confianza y la anticipación. El sentimiento menos representado en la obra es el de sorpresa (algo esperable de la fantasía clásica, no es muy dada a giros de guión). Este análisis también se puede estudiar en virtud de cada una de las tres películas, para lo cual basta por definir tres marcos de datos a partir del original en su estructura limpia, tokens, en el que se efectúe un filtro por el título de la película. Tras definir estos tres marcos de datos se combinan mediante rbind() y se realiza el diagrama de barras.

El diagrama de barras obtenido nos dice que a pesar de tener más diálogos Las Dos Torres es El Señor de los Anillos: El Retorno del Rey la película que más sentimientos tiene asociados. Esto tiene sentido ya que en la última de las películas es donde se resuelven la mayoría de los conflictos.

Otra tarea interesante que se puede hacer es ver qué palabras contribuyen más a cada sentimiento en el texto a analizar. Para que la visualización sea más nítida se puede utilizar facet_wrap(), que permite separar los gráficos dada una variable categórica, que en este caso son los sentimientos. Así, se van a ver las diez palabras que más impactan sobre cada uno de los sentimientos recogidos en el sistema nrc.

Se puede verificar que la palabra muerte es una de las más destacadas siendo la que más impacto tiene en los sentimientos de ira, miedo y tristeza, y estando presente también en las categorías de asco, negativo y sorpresa. Ahora, se va a realiza el análisis de sentimientos para cada personaje.

Como era de esperar el personaje más negativo es Sam, seguido de Faramir y Theoden, mientras que los de sentimientos más positivos se encuentran en Bilbo, Frodo, Pippin y el propio Smeagol, que se separa de su alter ego Gollum como unidad de análisis independiente. Los hobbits representan la raza más positiva de las películas y los libros, y esto se manifiesta no sólo en las descripciones que hace Tolkien de los mismos, sino también en los diálogos de sus personajes, a excepción de Sam, cuyos diálogos han de entenderse en su debido contexto, es decir, como contrapeso al personaje de Frodo, ante todo en la relación que éste guarda con Gollum.

Finalmente, se hace uso del sistema de categorización AFINN de Finn Årup Nielsen. Se parte de

A diferencia de lo que sucedía con el sistema de categorización nrc, con AFINN se han obtenido más palabras negativas que positivas. Como se hizo anteriormente, se mirará qué palabras contribuyen de forma crítica a los sentimientos positivos y negativos, lo cual ahora se hará multiplicando la puntuación de cada palabra por el número de ocurrencias.

Así, se puede comprobar que la palabra bien es la que más impacta sobre los sentimientos positivos y la palabra no es la que más fuerza tiene en los sentimientos negativos.

Bigramas & Trigramas

Hasta ahora sólo se han llevado a cabo el análisis en virtud de unigramas. Ahora bien, hay ocasiones en las que esto genera confusión y errores puesto que se prescinde del contexto en el que se encuentra la palabra. Así, por ejemplo, viendo los resultados del último apartado de la sección anterior, la palabra bien, que es aquella que más contribuía a los sentimientos positivos, puede encontrarse en oraciones negativas, a saber, “Eso no está bien”, así como la palabra que tenía más peso sobre los sentimientos negativos, que era no, puede enmarcarse en un contexto positivo, a saber, “No estás solo, compañero”. Es aquí donde nace la necesidad de utilizar lexicones de orden superior como los bigramas y trigramas entre otros. Las nubes de palabras que se generan mediante éstos son muy utilizadas en el campo de la Inteligencia Artificial para la creación de bots que copien un estilo. Así, por medio una network o grafo de bigramas o trigramas se puede imitar el estilo de un autor, que para este ejemplo sería Tolkien.

Un bigrama y un trigrama realmente se definen como la secuencia resultante de la unión de dos y tres elementos adyacentes de una cadena de tokens respectivamente. Estos elementos adyacentes pueden ser letras, sílabas o palabras, siendo en el ejemplo que aquí se trata palabras,.

La Comunidad del Anillo

Comenzaremos por hacer el análisis de La Comunidad del Anillo. En primer lugar se muestra la nube de las palabras más frecuente, que, como cabía esperar son Frodo, anillo y único. Esta última palabra habría que ponerla entre comillas porque es la traducción que se hace al español. En inglés las oraciones para referirse al Anillo Único son por ejemplo One ring to rule them all, one ring to bind them o He persuaded Elven smiths in Eregion to create Rings of Power, and secretly forged the One Ring to control the other Rings, es decir, es siempre one, no hay distinción. Si se trabajara con los diálogos en castellano se tendrían dos palabras, un (anillo) y (el Anillo) Único, por lo que esta palabra no sería tan destacada como otras tales como Gandalf, por ejemplo.

A continuación, se construyen los bigramas y trigramas, y se seleccionan los diez más frecuentes.

Por último se muestran los diez bigramas y diez trigramas más frecuentes de la película.

Las Dos Torres

Se procede a realizar el mismo análisis para Las Dos Torres. Primero se construye una nube de palabras para ver aquellas que aparecen con más frecuencia. Ahora destacan palabras como venir, deber, ahora, señor y sí, además de único y Frodo.

Se elaboran los bigramas y trigramas y se seleccionan los diez mejores.

Se grafican los bigramas y trigramas más frecuentes. Como se puede ver, en este caso, el vocabulario es más adulto que el de La Comunidad del Anillo. Esto es debido a que esta última es una obra más enfocada en la aventura; en ella Tolkien se centra el personaje de Frodo Bolsón, en su huida de La Comarca a causa de la persecución que ejercen sobre él los esbirros del Sauron para arrebatarle el Anillo Único. En esta segunda obra hay varias historias que se narran de forma paralela y se focaliza más en las guerras de Gondor con Mordor (que es la antesala de la batalla de los Campos de Pelennor de El Retorno del Rey) y de Rohan con Isengard, lo cual explica que este cambio en el tono de la historia que se manifiesta en el vocabulario.

El Retorno del Rey

Por último, se dibuja la nube de palabras de El Retorno del Rey que, como se puede comprobar, es muy parecida a la de la segunda película, pero con más presencia de la palabra Frodo, lo cual puede deberse a que al final de la película, tras la gran batalla, la obra pasa a centrarse de forma exclusiva en cerrar el arco de Frodo y todos los personajes dirigen su atención hacia él.

De nuevo se construyen los bigramas y trigramas utilizando como filtro el nombre de la película.

Se ejecutan las gráficas y se puede ver que vuelve a darse una infantilización de los diálogos, si bien no es tan exagerada como la presente en los bigramas y trigramas de La Comunidad del Anillo. Esto cobra sentido si se miran de cerca los análisis previos. En Las Dos Torres se había visto que había más diálogo, pero menos sentimientos que en El Retorno del Rey y esto se manifiesta en la complejidad de los discursos. En El Retorno del Rey Tolkien trata de forma más extensa la acción, gran parte de la película consiste en la batalla de los Campos de Pelennor, mientras que Las Dos Torres existe un equilibrio entre la batalla del Abismo de Helm y la aventura o la situación del pueblo de Rohan; hay menos acción, y, por lo tanto, menos sentimientos asociados. Otra razón más por la que sin lugar a dudas, Las Dos Torres es la mejor película de la trilogía.

Grafos con Bigramas

Para visualizar las relaciones que guardan las palabras se puede recurrir a las redes o grafos. Se va a crear un grafo para los bigramas de toda la trilogía. Para ello primero se crean los bigramas a partir del texto que se ha limpiado. Tras ello se separan los bigramas en sus dos partes, se filtran por aquello que presentan más de cinco ocurrencias y se crea un objeto igrpah. Se de forma a los puentes que unirán los nodos, que en este caso son closed y de quince pulgadas; y finalmente se realiza la gráfica con la función ggraph().

Palabras más Frecuentes por Personaje

En esta última sección sobre los diálogos se va a estudiar cuáles son las palabras más frecuentes para los principales personajes. Para ello, primero se definen las stop words de las que se prescindirá. Después, se crea una variable para los diálogos, se filtra por personaje, se aplica la función unnest_tokens(), para tokenizar, y se hace el anti_join() con el marco de palabras stop. Con este nuevo conjunto de datos que recoge los tokens, se realiza el conteo de palabras, se agrupa por personaje, se ordena por frecuencia, se seleccionan las principales diez palabras, se desagrupa y se crea una nueva variable, word2, que recoge la palabra y el personaje que la enuncia. Finalmente se crea el gráfico con ggplot2.

Se puede mejorar el análisis previo mediante la función bind_tf_idf(), la cual permite obtener términos más relevantes y característicos asociados a los personajes. La idea subyacente de esta función es el TFIDF (term frequency–inverse document frequency), que es un estadístico que refleja la importancia de una palabra para un documento en una colección o corpus. Para ello, este estadístico disminuye el peso de las palabras de uso común (que no han sido consideradas como stop words por el investigador) y aumenta el peso de las palabras de uso poco frecuente en una colección o corpus. Así, si el término aparece en todos los documentos, no es probable que sea revelador.

Marcos de Datos de los Personajes

Para finalizar este caso práctico se analizarán los personajes. Primero, se carga el marco de datos que contiene información sobre los personajes (sexo, edad, altura, reino, raza…) y se miran sus primeras y últimas filas. En este caso se añade la codificación UTF-8 porque Tolkien utiliza vocales no inglesas para los nombres de muchos personajes secundarios (Ar-Adûnakhôr) y lugares (Númenor) que no están recogidas en el formato ASCII.

    birth             death gender                            hair height
1                           Female                                       
2 TA 2978 February 26 ,3019   Male Dark (book) Light brown (movie)       
3               March ,3019   Male                                       
4  TA 280            TA 515   Male                                       
5                             Male                                       
6 SA 2709           SA 2962   Male                                       
7                             Male                                       
                    name   race   realm       spouse
1                 Adanel    Men              Belemir
2                Boromir    Men                     
3                 Lagduf   Orcs                     
4                 Tarcil    Men   Arnor Unnamed wife
5 Fire-drake of Gondolin Dragon                     
6           Ar-Adûnakhôr    Men Númenor Unnamed wife
7                 Annael  Elves                     
                                 birth           death gender   hair height
905                                                                        
906                                                                        
907                     Mid ,First Age          FA 495 Female              
908                                                                        
909 YT during the ,Noontide of Valinor          FA 455   Male Golden       
910                            TA 2917         TA 3010   Male              
911                    Before ,TA 1944 Late ,Third Age   Male              
          name  race realm                                spouse
905      Aghan                                                  
906    Agathor                                                  
907      Aerin   Men                                      Brodda
908   Aerandir                                                  
909     Aegnor Elves       Loved ,Andreth but remained unmarried
910 Adrahil II   Men                                Unnamed wife
911  Adrahil I   Men                                            

Se realiza una exploración superficial del marco de datos con las funciones str() y summary(). Con ello se tiene que el marco de datos consta de 911 sujetos y 9 variables, que son el nacimiento, la muerte, el sexo, el pelo, la altura, el nombre, la raza, el reino y el nombre del cónyuge.

'data.frame':   911 obs. of  9 variables:
 $ birth : chr  "" "TA 2978" "" "TA 280" ...
 $ death : chr  "" "February 26 ,3019" "March ,3019" "TA 515" ...
 $ gender: chr  "Female" "Male" "Male" "Male" ...
 $ hair  : chr  "" "Dark (book) Light brown (movie)" "" "" ...
 $ height: chr  "" "" "" "" ...
 $ name  : chr  "Adanel" "Boromir" "Lagduf" "Tarcil" ...
 $ race  : chr  "Men" "Men" "Orcs" "Men" ...
 $ realm : chr  "" "" "" "Arnor" ...
 $ spouse: chr  "Belemir" "" "" "Unnamed wife" ...
    birth              death              gender              hair          
 Length:911         Length:911         Length:911         Length:911        
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
    height              name               race              realm          
 Length:911         Length:911         Length:911         Length:911        
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
    spouse         
 Length:911        
 Class :character  
 Mode  :character  

Se inicia así el análisis de datos buscando qué raza es la predominante en el mundo diseñado por Tolkien. Como es de esperar si se han visto las películas, la gran mayoría de los personajes son humanos, y los minoritarios son los maiar, que son ainur, una clase de seres espirituales, creados por Ilúvatar a partir de su pensamiento junto con los valar. Son espíritus de menor jerarquía que los valar y sus aventuras se narran en El Silmarilllion, cuya lectura recomiendo.

Se estudia a continuación el sexo de los personajes.

La mayor parte de los personajes son varones, tal y como era previsible conociendo la época en que fuera escrito. Para ser más precisos, miremos la distribución de sexos por raza. Hay varios aspectos interesates entre los que destacan que los orcos no tienen hembras (no se habla del sexo de los orcos en la obra, así que realmente hablar de hembras o varones tampoco tiene mucho sentido, por lo que finalmente no se muestra en la gráfica), al igual que los enanos. Esto último es algo a lo que hace referencia Gimli en un diálogo con Eowyn y Aragorn en Las Dos Torres. El diálogo reza así:

  • Gimli: Es cierto, no se ven muchas mujeres enanas. En realidad, se nos parecen tanto en la voz y en apariencia que suelen tomarlas por enanos varones. Eowyn se giró hacia Aragorn.
  • Aragorn: Es por la barba.
  • Gimli: De ahí surgió la creencia establecida de que no hay mujeres enanas y de que los enanos brotan de agujeros en el suelo. Lo que resulta ridículo.

Además, la distribución de sexos dentro de los Ainur es pareja. Esto puede tener su origen en que los politeísmos en los que se inspira Tolkien para definir su cosmogonía suelen tener un equilibrio entre deidades femeninas y masculinas.

Un análisis superficial podría hablar de la falta de igualdad de sexos, pero eso sería no conocer la obra de Tolkien. El personaje más poderoso de la Tierra Media en la Tercera Edad es la elfa Galadriel, también conocida como La Dama de Lórien, tras la muerte de Gil-Galad en la guerra contra Sauron al final de la Segunda Edad (año 3441 SE). Por otro lado, un personaje crítico y reivindicativo del feminismo es Eowyn, quien derrota el Rey Brujo en la batalla de Pelennor. Además, Tolkien (y los escritos publicados más tarde por su hijo) tiene otras obras en las que las mujeres tienen un gran protagonismo como son La Caída de Gondolin y La Historia de Belen & Lúthien. Dicho esto, analizado el sexo, se mira qué reinos presentan más personajes.

Los reinos más destacados son Gondor y Númenor, seguidos de Rohan y, en cuarta posición, de Arnor. Por último, se hará el análisis por el color del pelo.

Y con esto termina este extenso caso práctico. Espero que te haya gustado.

Caso Práctico de Probabilidad

De media una papelería vende treinta bolígrafos al día con una desviación típica de cinco, y una media de veinte cuadernos al día con una desviación típica de cuatro. Se sabe que el coeficiente de correlación entre ambos eventos es de 0.7. Dicho esto, ¿cuál sería la probabilidad de que los artículos vendidos de ambos tipos de productos estuviera entre 4300 y 4600 unidades?

\[P(4300 \leq X + Y \leq 4600)\]

Por este enunciado se tienen tres variables:

  • \(X\): número de bolígrafos vendidos. Su esperanza es \(E(X)=30\) y su varianza es de \(var(X)=25\).
  • \(Y\): número de cuadernos vendidos. Su esperanza es \(E(X)=20\) y su varianza es de \(var(X)=16\).
  • \(D\): número de productos vendidos. Su esperanza es \(E(D)=E(X)+E(Y)=50\) y se conoce que la correlación es de \(\rho_{xy}=0.7\).

Además se sabe que el coeficiente de correlación se expresa como:

\[\rho _{xy} = \frac{Cov(X,Y)}{\sigma_X\sigma_y}\]

Despejando la fórmula se calcula la covarianza como:

[1] 14

Con esto se calcula la varianza de \(D\), al no ser \(X\) e \(Y\) independientes, como:

\[var(D) = var(X)+var(Y)+2\cdot Cov(X,Y)\]

[1] 69

Se define ahora una nueva variable, \(S_{90}\) que defina el número de ventas al trimestre, es decir, \(S_{90}=D_1+D_2+...+D_n \ \ D_i \thicksim D\). En este supuesto, su esperanza será \(E(S_{90})=90 \cdot E(D)\).

[1] 4500
[1] 6210

Para calcular \(P(4300 \leq X + Y \leq 4600)\) se puede utilizar el Teorema Central del Límite y aproximar a una distribución normal estándar (Z) tal que \(P(4300 \leq X + Y \leq 4600) \approxeq P(4300.5 \leq X + Y \leq 4600.5)\), lo cual se estandariza mediante:

\[P(4300.5 \leq X + Y \leq 4600.5)= P \left( \frac{4300.5-4500}{78.8} \leq \frac{S_{90}-ES_{90}}{\sigma_{S_{90}}} \leq \frac{4600.5-4500}{78.8} \right) = \\ P \left( \frac{4300.5-4500}{78.8} \leq Z \leq \frac{4600.5-4500}{78.8} \right)\]

Esto resulta al consultar las tablas de Z en una probabilidad del 89.4% de vender tal intervalo de unidades de los productos.

\[P( -2.53\leq Z \leq 1.28) = F_z(1.28)-F_z(-2.53)=0.8997-0.0057=0.894=89.4%\]

La Validación Cruzada

La Validación Cruzada es una técnica comúnmente utilizada para evaluar los resultados de análisis estadísticos y verificar que los resultados hallados son independientes de la partición que se ha hecho de los conjuntos de entrenamiento y prueba. Esta técnica es una mejora del método de retención o hold-out method visto en la sección anterior. El peligro de la técnica de retención es que el método de partición puede condicionar fuertemente la precisión obtenida en la evaluación del modelo. Esto lleva al conocido como overfitting, que es un efecto por el cual el modelo diseñado se ajusta demasiado bien a los datos de entrenamiento suministrados, esto es, no es generalizable. Así pues, en este contexto la validación cruzada consiste básicamente en repetir el proceso de partición de los datos tantas veces como desee el investigador y calcular la media aritmética dada por las medias de evaluación sobre las particiones realizadas. Se ejecuta en operaciones de predicción de datos, pero también es aplicable para clasificadores, y busca evaluar la precisión del modelo que se quiere llevar a la práctica. Es ampliamente utilizada para modelos dentro del campo de la Inteligencia Artificial.

Hay varias opciones a la hora de realizar la validación cruzada. Una de ellas es la que realiza k iteraciones, conocida como K-fold Cross Validation. En este caso los datos de la muestra se subdividen en k conjuntos de forma que uno de los subconjuntos se utiliza como los datos de prueba y el resto, \(k-1\), como los datos de entrenamiento. Este proceso se repite cambiando los roles de modo que cada iteración un subconjunto hará las veces de conjunto de prueba. Una vez hecho esto se calcula la media de los resultados obtenidos para el conjunto de las iteraciones. La desventaja es que tiene un mayor coste computacional y, por tanto, es preciso elegir bien la k. La elección del parámetro k o número de iteraciones depende de múltiples factores, dentro de los cuales tiene peso la medida del conjunto. Lo más común es utilizar diez iteraciones (10-fold). El error se calculará entonces como la media del conjunto de errores obtenidos por iteración siendo en la siguiente fórmula K el número de iteraciones ejecutadas:

\[E = \frac{1}{K} \cdot\sum_{i=1}^{K}E_i\]

Otro tipo de validación cruzada es la Random Cross Validation. En ese método la partición en los conjuntos de entrenamiento y prueba por iteración es aleatoria, no se sigue ningún patrón. El cálculo de los errores es de nuevo el mismo, a saber, la media aritmética de los resultados obtenidos en las iteraciones. La ventaja es que la división de los datos no depende del número de iteraciones que se realicen. No obstante, con esta técnica hay muestras que quedan sin evaluar y otras que se evalúan de forma repetida, es decir, que los conjuntos de prueba y de entrenamiento son susceptibles de sufrir solapamiento entre iteraciones.

\[E = \frac{1}{K} \cdot\sum_{i=1}^{K}E_i\]

También se tiene la técnica Leave One Out Cross Validation, LOOCV para los amigos, que supone que para cada iteración se tenga solamente una sola muestra o sujeto para los datos de prueba tal que el resto sea elegido como conjunto de entrenamiento. El error en este caso tiende a ser bajo, pero es mucho más costoso en términos computacionales frente a las anteriores. La diferencia en la fórmula para hallar el error es que ahora se trabaja con N iteraciones.

\[E = \frac{1}{N} \cdot\sum_{i=1}^{N}E_i\]

Este conjunto de técnicas se suele utilizar para la comparación de clasificadores en proyectos empresariales. Por ejemplo, se tiene un conjunto de imágenes personas y se pretende saber qué modelo de entre los que se han diseñado (k-NN, VSM, RNA) es mejor para ser aplicado sobre una cámara que realice este proceso de clasificación. Con los métodos de validación cruzada se pueden realizar estas comparaciones de forma simultánea para evaluar la precisión y elegir el mejor de los modelos clasificatorios.